모션 디자인에 대해 자세히 알아보기: 고급 iOS 전환
게시 됨: 2020-11-09창의성은 사용자에게 깊은 인상을 남기고자 하는 끊임없는 열망을 수반합니다. 수세기 동안 인간은 가장 자연스러운 상호 작용을 재현하기 위해 사용 가능한 수단을 조작하려고 시도했으며, 자연을 근본적인 예로 들어 보았습니다.
세계를 발견할 때 사람은 주변 세계의 미묘한 세부 사항에 점점 더 민감해지며, 이는 본능적으로 생물과 인공물을 구별할 수 있게 합니다. 이 경계는 소프트웨어가 인공적으로 생성된 세계에서 자신의 경험을 자연스러운 것으로 설명하는 환경을 만드는 것을 목표로 하는 기술의 발전으로 흐려집니다.

앱 디자인에 자연을 반영
이 기사에서는 대부분의 iOS 애플리케이션에서 일상적인 요소의 대화형 애니메이션에서 모양 변형의 예를 사용하여 테두리를 흐리게 처리하는 프로세스를 소개합니다. 자연을 모방하는 한 가지 방법은 시간에 따라 대상의 위치를 다양하게 변형하는 것입니다. 애니메이션 시간의 예시적인 기능은 다음과 같습니다.

기하학적 변환을 추가하여 시간을 올바르게 사용하면 무한한 효과를 얻을 수 있습니다 . 오늘날의 디자인과 기술의 가능성을 보여주기 위해 우리의 소프트웨어 개발 회사에서 개발한 인기 있는 솔루션을 포함하는 Motion Patterns 애플리케이션이 만들어졌습니다. 나는 작가가 아니라 프로그래머이고 실제 사례보다 더 좋은 것은 없기 때문에 이 멋진 세계로 여러분을 초대할 수 밖에 없습니다!
발견할 샘플

모션 디자인이 어떻게 평범한 디자인을 특별한 디자인으로 바꿀 수 있는지 살펴봅시다! 아래 예에서 왼쪽에는 기본 iOS 애니메이션만 사용하는 응용 프로그램이 있고 오른쪽에는 일부 개선된 동일한 응용 프로그램 버전이 있습니다.
"더 깊이" 효과
이것은 보기의 두 상태 간의 변환을 사용하는 전환 입니다. 컬렉션을 기반으로 구축된 특정 셀을 선택한 후 개별 요소 *를 변환하여 요소의 세부 정보로 전환됩니다. 추가 솔루션은 응용 프로그램 사용을 용이하게 하는 대화식 전환을 사용하는 것입니다.
*실제로 시작과 끝 사이의 전환에 참여하는 임시 보기에서 데이터 요소를 복사/매핑하지만 이 기사의 뒷부분에서 이에 대해 설명하겠습니다...
"가장자리 엿보기" 효과
액션에서 스크롤 뷰 애니메이션을 사용 하면 큐브 효과를 위해 이미지가 3D 형태로 변형됩니다. 효과를 담당하는 주요 요인은 스크롤 뷰의 오프셋입니다.
"점을 연결" 효과
이것은 미니어처 개체를 전체 화면으로 변환하는 장면 사이의 전환입니다. 이 목적으로 사용되는 컬렉션은 동시에 작동하며 한 화면의 변경은 다른 화면의 이동에 해당합니다. 또한 배경의 미니어처에 들어가면 장면 사이를 스와이프할 때 시차 효과가 나타납니다.
"모양 이동" 효과
마지막 유형의 애니메이션은 Lottie 라이브러리를 사용하는 간단한 애니메이션입니다. 아이콘에 애니메이션을 적용하는 가장 일반적인 용도입니다. 이 경우 탭 표시줄에 있는 아이콘입니다. 또한 적절한 탭을 변경하여 특정 방향으로의 전환 애니메이션을 사용하여 인터랙션 효과를 더욱 강화했습니다.
자세히 알아보기: 첫 번째 모션 디자인 패턴
이제 요점에 도달할 시간입니다... 우리는 이러한 예를 제어하는 메커니즘의 구조에 대해 더 깊이 들어갈 필요가 있습니다.
이 글에서는 구체적인 내용은 생략하고 추상적인 사용법과 함께 'Dive Deeper'라고 명명 한 첫 번째 모션 디자인 패턴 을 소개합니다. 앞으로 모든 사람이 제한 없이 정확한 코드와 전체 저장소를 사용할 수 있도록 할 계획입니다.
프로젝트의 아키텍처와 적용된 엄격한 프로그래밍 디자인 패턴은 현재 우선 순위가 아닙니다. 우리는 애니메이션과 전환에 중점을 둡니다.
이 기사에서는 장면 전환 중 보기를 관리하기 위해 제공되는 두 가지 기능 세트를 사용할 것입니다. 따라서 이 기사는 UIKit 및 Swift 구문에 비교적 익숙한 사람들을 대상으로 한다는 점을 지적하고 싶습니다.
https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning
https://developer.apple.com/documentation/uikit/uipercentdriveninteractivetransition
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html
첫째: 구조
주어진 솔루션을 구현하는 기본 버전의 경우 전환에 관련된 보기에 대한 필요한 정보를 제공하고 전환 자체 및 상호 작용을 제어하는 여러 도우미 클래스가 필요합니다.

전환 관리를 담당하는 기본 클래스인 프록시는 TransitionAnimation입니다. 전환이 수행되는 방식을 결정하고 Apple 팀에서 제공하는 작업을 수행하는 데 필요한 표준 기능을 다룹니다.
/// 이것은 지정된 기간 동안 표시 및 해제 시 다른 동작을 갖는 전환에 대한 기본 클래스입니다. 오픈 클래스 TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { /// 전환을 표시하는지 아니면 해제하는지 나타냅니다. var 표시: Bool = true /// 전체 전환이 발생하는 시간 간격. private let 기간: TimeInterval /// 기본값이 있는 전환 애니메이터의 기본 초기화자. /// - 매개변수 지속 시간: 전체 전환이 발생하는 시간 간격입니다. /// - 매개변수 표시: 전환을 표시하는지 또는 해제하는지 표시합니다. 공개 초기화(지속 시간: TimeInterval = 0.5, 표시: Bool = true) { self.duration = 지속 시간 self.presenting = 발표 super.init() } /// 전환 기간을 결정합니다. /// - 매개변수 transitionContext: 현재 전환의 컨텍스트입니다. /// - 반환값: 애니메이터 초기화 시 지정된 기간. public func transitionDuration(transitionContext 사용: UIViewControllerContextTransitioning?) -> TimeInterval { 자체 반환 } /// 전환 애니메이터의 심장, 이 함수에서 전환이 발생합니다. /// - 중요: 보다 구체적인 유형의 전환에서 이 함수를 재정의하는 것은 애니메이션을 수행하는 데 중요합니다. /// - 매개변수 transitionContext: 전환 컨텍스트. public func animateTransition(transitionContext 사용: UIViewControllerContextTransitioning) { } }
TransitionAnimator를 기반으로 TransformTransition 파일을 생성합니다. 이 파일의 작업은 특정 전환을 수행하는 것입니다. 전환이 포함된 전환(부모)
/// 변환 전환 구현. 오픈 클래스 TransformTransition: TransitionAnimator { /// 전환에 필요한 모든 사양을 포함하는 뷰 모델. private var viewModel: TransformViewModel /// 변환 전환의 기본 초기화자. /// - 매개변수 viewModel: 변환 전환의 보기 모델입니다. /// - 매개변수 지속 시간: 전환 지속 시간. init(viewModel: TransformViewModel, 기간: TimeInterval) { self.viewModel = 뷰 모델 super.init(지속 시간: 지속 시간, 표시: viewModel.presenting) }
TransformTransition 클래스의 구성에는 이름에서 알 수 있듯이 이 전환이 적용되는 보기 모델의 메커니즘을 알려주는 TransformViewModel이 포함됩니다.
/// 기본 정보를 담고 있는 변환 전환 모델을 봅니다. 최종 클래스 TransformViewModel { /// 변환 전환이 보기를 표시하는지 아니면 닫는지를 나타냅니다. 발표하자: Bool /// 각 보기에 대한 변환에 대한 사양이 있는 모델 배열. let 모델: [TransformModel] /// 변환 뷰 모델의 기본 초기화자. /// - 매개변수 표시: 변환 전환을 표시하는지 아니면 해제하는지 나타냅니다. /// - 매개변수 모델: 각 보기에 대한 변환에 대한 사양이 있는 모델 배열입니다. init(presenting: Bool, 모델: [TransformModel]) { self.presenting = 발표 self.models = 모델 } }
변환 모델은 부모에 있는 전환과 관련된 뷰의 특정 요소를 설명하는 보조 클래스입니다. 일반적으로 변환할 수 있는 컨트롤러의 뷰입니다.
트랜지션의 경우, 이 트랜지션은 주어진 상태 사이의 특정 뷰의 동작으로 이루어지기 때문에 반드시 필요한 단계입니다.
두 번째: 구현
전환을 시작하는 뷰 모델을 Transformable로 확장하여 필요한 모든 요소를 준비하는 기능을 구현하도록 합니다. 이 함수의 크기는 매우 빠르게 커질 수 있으므로 예를 들어 요소별로 더 작은 부분으로 나누는 것이 좋습니다.
/// 변환 전환을 수행하려는 클래스에 대한 프로토콜입니다. 프로토콜 변환 가능: ViewModel { /// 전환에 관련된 뷰 모델을 준비합니다. /// - 매개변수 fromView: 전환이 시작되는 뷰 /// - 매개변수 toView: 전환이 진행되는 보기입니다. /// - 매개변수 표시: 표시 중인지 아니면 해제 중인지 나타냅니다. /// - 반환값: 각 보기에 대한 변환 변환에 필요한 모든 정보를 보유하는 구조의 배열입니다. func prepareTransitionModels(fromView: UIView, toView: UIView, 표시: Bool) -> [TransformModel] }
가정은 변환에 참여하는 뷰의 데이터를 검색하는 방법을 말하는 것이 아닙니다. 내 예에서는 주어진 보기를 나타내는 태그를 사용했습니다. 구현의 이 부분에서 자유로이 손을 댈 수 있습니다.

특정 보기 변환 모델(TransformModel)은 전체 목록에서 가장 작은 모델입니다. 시작 보기, 전환 보기, 시작 프레임, 끝 프레임, 시작 중심, 끝 중심, 동시 애니메이션 및 종료 작업과 같은 주요 변환 정보로 구성됩니다. 대부분의 매개변수는 변환 중에 사용할 필요가 없으므로 고유한 기본값이 있습니다. 최소한의 결과를 얻으려면 필요한 것만 사용하면 됩니다.
/// 기본값이 있는 변환 모델의 기본 초기화자. /// - 매개변수 initialView: 전환이 시작되는 뷰입니다. /// - 매개변수 phantomView: 변환 전환 중에 표시되는 보기입니다. /// - 매개변수 initialFrame: 변환 전환을 시작하는 뷰 프레임입니다. /// - 매개변수 finalFrame: 변환 전환이 끝날 때 표시될 뷰 프레임입니다. /// - Parameter initialCenter: 초기 중심 시점이 초기 시점의 중심과 다를 때 필요합니다. /// - 매개변수 finalCenter: 최종 뷰의 중심점이 최종 뷰의 중심과 다를 때 필요합니다. /// - Parameter parallelAnimation: 변환 전환 중에 수행되는 뷰의 추가 애니메이션입니다. /// - 매개변수 완성: 변환 전환 후 트리거된 코드 블록입니다. /// - 참고: 가장 최소한의 변형 전환을 수행하려면 초기 보기만 필요합니다. init(초기 보기: UIView, 팬텀뷰: UIView = UIView(), 초기 프레임: CGRect = CGRect(), 최종 프레임: CGRect = CGRect(), initialCenter: CGPoint? = 없음, finalCenter: CGPoint? = 없음, parallelAnimation: (() -> 무효)? = 없음, 완료: (() -> 무효)? = 없음) { self.initialView = 초기보기 self.phantomView = 팬텀뷰 self.initialFrame = 초기 프레임 self.finalFrame = finalFrame self.parallelAnimation = 병렬 애니메이션 self.completion = 완료 self.initialCenter = initialCenter ?? CGPoint(x: initialFrame.midX, y: initialFrame.midY) self.finalCenter = finalCenter ?? CGPoint(x: finalFrame.midX, y: finalFrame.midY) }
팬텀 뷰에 주의가 집중되었을 수 있습니다. iOS 전환의 워크플로를 설명하는 시간입니다. 최대한 짧은 형태로…

사용자가 다음 장면으로 이동하려는 경우 iOS는 시작(파란색) 및 대상(녹색) 컨트롤러를 메모리에 복사하여 특정 컨트롤러를 준비합니다. 다음으로, 두 장면 사이의 전환 보기를 시뮬레이션하는 것 외에 특별한 기능을 포함하지 않는 '멍청한' 보기인 컨테이너를 포함하는 전환 조정자를 통해 전환 컨텍스트가 생성됩니다.
전환 작업의 핵심 원칙은 전환 컨텍스트에 실제 보기를 추가하지 않는 것입니다. 전환이 끝나면 컨테이너에 추가된 보기와 함께 모든 컨텍스트가 할당 해제되기 때문입니다. 이는 전환 중에만 존재한 다음 제거되는 보기입니다.
따라서 실제 보기의 복제본인 가상 보기를 사용하는 것이 이러한 전환에 대한 중요한 솔루션입니다.

이 경우 모양과 크기를 변경하여 한 보기를 다른 보기로 변환하는 전환이 있습니다. 이렇게 하려면 전환이 시작될 때 지정된 요소의 PhantomView를 만들고 컨테이너에 추가합니다. FadeView는 전체 전환에 부드러움을 추가하는 보조 보기입니다.
/// 변환이 수행되는 변환 변환의 핵심입니다. 'TransitionAnimator.animateTransition(...)' 재정의. /// - 매개변수 transitionContext: 현재 변환 전환의 컨텍스트입니다. open func animateTransition을 재정의(transitionContext 사용: UIViewControllerContextTransitioning) { 가드 toViewController = transitionContext.view(forKey: .to), let fromViewController = transitionContext.view(forKey: .from) else { 반환 Log.unexpectedState() } let containerView = transitionContext.containerView 지속 시간 = transitionDuration(사용: transitionContext) 하자 let fadeView = toViewController.makeFadeView(불투명도: (!presenting).cgFloatValue) 하자 모델 = viewModel.models letpresentedView = 발표? toViewController : fromViewController models.forEach { $0.initialView.isHidden = true } PresentView.isHidden = true containerView.addSubview(toViewController) 제시하는 경우 { containerView.insertSubview(fadeView, belowSubview: toViewController) } 또 다른 { containerView.addSubview(fadeView) } containerView.addSubviews(viewModel.models.map { $0.phantomView })
다음 단계에서는 변형을 통해 대상 모양으로 변형하고 프레젠테이션인지 리콜인지에 따라 추가 작업을 수행하여 특정 보기를 정리합니다. 이것이 이 전환의 전체 레시피입니다.
let animations: () -> Void = { [약한 자아] in 가드 let self = self else { return Log.unexpectedState() } fadeView.alpha = self.presenting.cgFloatValue model.forEach { let center = self.presenting ? $0.finalCenter : $0.initialCenter let transform = self.presenting ? $0.presentTransform : $0.dismissTransform $0.phantomView.setTransformAndCenter(변환, 중앙) } models.compactMap { $0.parallelAnimation }.forEach { $0() } } 완료하자: (Bool) -> Void = { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) PresentView.isHidden = 거짓 models.compactMap { $0.completion }.forEach { $0() } model.forEach { $0.initialView.isHidden = 거짓 } if!self.presenting && transitionContext.transitionWasCancelled { toViewController.removeFromSuperview() fadeView.removeFromSuperview() } } UIView.animate(withDuration: 지속 시간, 지연: 0, SpringWithDamping 사용: 1, 초기스프링 속도: 0.5, 옵션: .curveEaseOut, 애니메이션: 애니메이션, 완료: 완료)
세 번째: 특별한 재료
모든 기능, 클래스 및 프로토콜을 결합한 후 결과는 다음과 같아야 합니다.
전환의 마지막 구성 요소는 완전한 상호 작용이 될 것입니다. 이를 위해 컨트롤러 보기에 추가된 팬 제스처인 TransitionInteractor…
/// 대화형 전환을 처리하는 중재자. 최종 클래스 TransitionInteractor: UIPercentDrivenInteractiveTransition { /// 전환이 시작되었는지 여부를 나타냅니다. var hasStarted = 거짓 /// 전환이 완료되어야 하는지 여부를 나타냅니다. var shouldFinish = 거짓 }
... 컨트롤러 본체에서도 초기화합니다.
/// 컬렉션 보기 항목에 대한 팬 제스처를 처리하고 전환을 관리합니다. @objc func handlePanGesture(_gestureRecognizer: UIPanGestureRecognizer) { 퍼센트 임계값을 보자: CGFloat = 0.1 let 번역 = 제스처 인식기.translation(in: view) verticalMovement = translation.y / view.bounds.height 하자 위쪽으로 이동하자 = fminf(Float(verticalMovement), 0.0) upwardMovementPercent = fminf(abs(upwardMovement), 0.9) 진행하자 = CGFloat(upwardMovementPercent) 가드 상호 작용자 = 상호 작용 컨트롤러 else { 반환 } 제스처 인식기.상태 전환 { 케이스 시작: 상호 작용자.hasStarted = true let tapPosition =gestureRecognizer.location(in: collectionView) showDetailViewControllerFrom(위치: 탭 위치) 케이스 변경: Interactor.shouldFinish = 진행률 > 퍼센트 임계값 상호작용자.업데이트(진행) 취소된 경우: 상호 작용자.hasStarted = 거짓 상호작용자.취소() 케이스 종료: 상호 작용자.hasStarted = 거짓 Interactor.shouldFinish ? 인터랙터.마감() : 인터랙터.취소() 기본: 부서지다 } }
준비된 상호 작용은 다음과 같아야 합니다.
모든 것이 계획대로 진행된다면 우리 애플리케이션은 사용자의 관점에서 훨씬 더 많은 것을 얻게 될 것입니다.

빙산의 정점만 발견
다음에는 모션 디자인 관련 이슈의 구현에 대해 다음 판에서 설명하겠습니다.
응용 프로그램, 디자인 및 소스 코드는 Miquido의 자산이며 재능 있는 디자이너와 프로그래머가 열정을 가지고 만들었습니다. 자세한 소스 코드는 향후 github 계정을 통해 제공될 예정입니다. 팔로우 부탁드립니다!
관심을 가져주셔서 감사합니다. 곧 만나요!