• New Animation
    • iOS7부터 블록 기반의 애니매이션 API 등장

      // 기존 API - 현재는 deprecated
      class func beginAnimations(_ animationID: String?, 
                         context: UnsafeMutableRawPointer?)
      class func commitAnimations()
      
      // 블록 기반 API
      class func animate(withDuration duration: TimeInterval, 
                   delay: TimeInterval, 
                 options: UIView.AnimationOptions = [], 
              animations: @escaping () -> Void, 
              completion: ((Bool) -> Void)? = nil)
      
      // 애니매이션 비활성화가 필요할 때 구역을 만들어 사용하던 것
      class func setAnimationsEnabled(Bool)
      
      // 블록 기반 API
      class func performWithoutAnimation(_ actionsWithoutAnimation: () -> Void)
      
      • Core Animation과의 관계: Animate 블록 안에서 view의 프로퍼티를 업데이트 하면, 뒤에 있는 CALayer에 CAAnimation 객체가 붙게 된다.
      • 스프링 애니매이션
        • DampingRatio: (0<r≤1.0)
        • 초기 스프링 속도(Initial Sping Velocity)
        • 다른 Animate메소드와 합성 가능
      class func animate(withDuration: TimeInterval,
      												 delay: TimeInterval,
      												 usingSpringWithDamping: CGFloat,
      												 initialSpringVelocity: CGFloat,
      												 options: UIView.AnimationOptions,
      												 animations: () -> Void,
      												 completion: ((Bool) -> Void)?)
      
      • 키-프레임 애니매이션

        • animateKeyframes(WithDuration: ) 은 CAKeyframeAnimation에 대응
        • animate(WithDuration: ...) 은 CABasicAnimation에 대응
        • 애니매이션 블록 안에서 키프레임을 추가
        class func animateKeyframes(withDuration duration: TimeInterval, 
                              delay: TimeInterval, 
                            options: UIView.KeyframeAnimationOptions = [], 
                         animations: @escaping () -> Void, 
                         completion: ((Bool) -> Void)? = nil)
        
        // 키프레임 애니매이션 내부에서 호출
        class func addKeyframe(withRelativeStartTime frameStartTime: Double, 
              relativeDuration frameDuration: Double, 
                    animations: @escaping () -> Void)
        
      • 스냅샷

        func snapshotView(afterScreenUpdates: Bool) -> UIView?
        func resizableSnapshotView(from: CGRect, afterScreenUpdates: Bool, withCapInsets: UIEdgeInsets) -> UIView?
        
      • UIKit Dynamics

        • 애니매이션 API와는 구분된다.
        • 다만 새로운 Transition API와의 연관성이 있다.
  • Custom view Controller Transition
    • 커스텀 가능한 부분

      • 프레젠테이션, 디스미스

        • modalPresentationStyle중에서도 fullScreen과 custom만 지원
        • 트랜지션 단계에서는 뷰컨트롤러가 윈도우 계층에서 사라지지 않는다.
        let vc: UIViewController = ...
        vc.modalPresentationStyle = .custom
        vc.transitioningDelegate = ...
        self.present(vc, animated: true, completion: nil)
        
      • UITabbarController

        // TabbarController의 인터페이스
        var selectedViewController: UIViewController?
        var selectedIndex: Int
        
        // 예제
        let secondTab = 1
        
        self.delegate = tabBarControllerDelegate
        self.selectedIndex = secondTab
        
      • UINavigationController이 UICollectionViewController를 만났을 때

        let layout1: UICollectionViewLayout
        let layout2: UICollectionViewLayout
        let layout3: UICollectionViewLayout
        
        let cvc1: UICollectionViewController = UICollectionViewController(collectionViewLayout: layout1)
        let cvc2: UICollectionViewController = UICollectionViewController(collectionViewLayout: layout2)
        let cvc3: UICollectionViewController = UICollectionViewController(collectionViewLayout: layout3)
        
        navigationController.pushViewController(cvc1, animated: true)
        cvc2.useLayoutToLayoutNavigationTransitions = true
        cvc3.useLayoutToLayoutNavigationTransitions = true
        navigationController.pushViewController(cvc2, animated: true)
        navigationController.pushViewController(cvc3, animated: true)
        navigationController.popViewController(animated: true)
        
    • 트랜지션이 뭔가?: 부모뷰의 자식뷰를 가진 뷰컨트롤러에, 자식 뷰컨트롤러를 붙이면서 부모 뷰컨에 붙어 있던 뷰를 자식 뷰컨의 뷰로 교체하는 것

      • 트랜지션 과정에서 컨테이너 뷰 안에서 기존뷰를 없애고 새로운 뷰를 추가하는 과정이 애니매이션으로 진행됨 → 순간적으로 두 개가 동시에 존재하게 되는 구간이 있다.

      • 전체 과정

        • 시작 상태: 최초 상태기 때문에 일관적인 뷰컨트롤러 계층과 뷰 계층을 가진다.
        • 유저에 의해서, 혹은 프로그램이 트랜지션 시작
        • 내부 구조 업데이트, 콜백 실행 등
        • 컨테이너뷰, 내부 뷰들의 시작 지점과 끝 지점 계산
        • 종료 상태에서의 뷰계층에서도 필요한 애니매이션이 있다면 실행
        • 애니매이션 끝: 내부 구조 업데이트, 콜백 실행 등
        • 종료 상태: 종료 상태에서도 일관적인 계층 구조
      • UIViewControllerContextTransitioning: 트랜지션 과정에서의 핵심 객체를 위한 프로토콜

        // 직접 만들거나 채택하는 프로토콜은 아니다.
        // 트랜지션 관련 정보를 얻거나, 트랜지션 상태를 시스템에 전달하는 역할을 한다.
        protocol UIViewControllerContextTransitioning: NSObjectProtocol {
        	var containerView: UIView { get }
          func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController?
        	func view(forKey key: UITransitionContextViewKey) -> UIView?
        	func initialFrame(for vc: UIViewController) -> CGRect
        	func finalFrame(for vc: UIViewController) -> CGRect
          func completeTransition(_ didComplete: Bool) // Animation의 completion 블록에서 호출해야 한다. 트랜지션 cancel시에는 false로 인자를 넘긴다.
        }
        
        // 애니매이션 트랜지션을 위한 프로토콜. 애니매이터 객체에 사용한다.
        // 인터렉션을 위해서는 UIViewControllerInteractiveTransitioning 프로토콜과 함께 써야 한다.
        protocol UIViewControllerAnimatedTransitioning: NSObjectProtocol {
        	// 해당 트랜지션의 소요 시간을 반환한다.
        	func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
        	// 애니메이션을 수행한다.
          // 인터렉티브한데 퍼센트 기반이 아닌 경우에만 해당 구현을 빈 채로 둘 수 있다.
        	func animateTransition(using transitionContext: UIViewControllerContextTransitioning)```swift
        // 직접 만들거나 채택하는 프로토콜은 아니다.
        // 트랜지션 관련 정보를 얻거나, 트랜지션 상태를 시스템에 전달하는 역할을 한다.
        protocol UIViewControllerContextTransitioning: NSObjectProtocol {
        	var containerView: UIView { get }
          func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController?
        	func view(forKey key: UITransitionContextViewKey) -> UIView?
        	func initialFrame(for vc: UIViewController) -> CGRect
        	func finalFrame(for vc: UIViewController) -> CGRect
          func completeTransition(_ didComplete: Bool) // Animation의 completion 블록에서 호출해야 한다. 트랜지션 cancel시에는 false로 인자를 넘긴다.
        }
        
        
    • 애니매이션 단계 중간에 낄 수 있는 delegate

      • UIViewControllerTransitionDelegate
      • UINavigationControllerDelegate
      • UITabBarControllerDelegate
    • 애니매이션 컨트롤러는 UIViewControllerAnimatedTransitioning을 채택해야 한다.

    • 인터렉션 컨트롤러는 UIViewControllerInteractiveTransitioning 프로토콜을 채택해야 한다.

    • 시스템에서 넘겨주는 객체는 UIViewControllerContextTransitioning 프로토콜을 채택한 채로 들어온다.

    • 커스텀 프레젠테이션

      • TransitionDelegate 설정

        // UINavigationControllerDelegate와 UITabBarControllerDelegate에도 유사 메소드가 있다.
        protocol UIViewControllerTransitioningDelegate {
        	optional func animationController(forPresented presented: UIViewController, 
                               presenting: UIViewController, 
                                   source: UIViewController) -> UIViewControllerAnimatedTransitioning?
        	optional func animationController(forDismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
        
          optional func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
        	optional func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
          
        	// 이건 다음 버전에 추가 
        	optional func presentationController(forPresented presented: UIViewController, 
                                  presenting: UIViewController?, 
                                      source: UIViewController) -> UIPresentationController?
        }
        
      • present

      • delegate 메소드 호출 → 여기서 애니매이터 객체를 만들어서 넘긴다.

        func animationController(forPresented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
        
      • transitionDuration을 호출해서 시간을 얻어낸다.

      • animateTransition 메소드를 호출한다.

        // pseudo code
        
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        	let inView = transitionContext.containerView
        	let toView = transitionContext.viewController(forKey: .to)!.view
        	let fromView = transitionContext.viewController(forKey: .from)!.view
        
        	let size = toEndFrame.size
        
        	if self.isPresentation {
        		inview.addSubView(toView)
        	} else {
        		inview.insertSubView(toView, belowSubview: fromView)
        	}	
        
        	UIView.animate(withDuration: self.transitionDuration, animation: {
        		if self.isPresentation {
        			toView.center = newCenter
        			toView.bounds = newBounds
        		} else {
        				// ...
        		}
        	}, completion: { finished in
        			transitionContext.completeTransition(finished)
        	}
        }
        
      • context를 통해서 completeTransition을 호출한다.

  • Interactive view Controller Transition
    • iOS 7.0부터 모든 UINavigationController는 팝 제스쳐 지원

    • 커스텀 트랜지션에 대한 상호작용 지원 → UIViewControllerInteractiveTransitioning

      • 제스처 기반이 아니여도 된다.
      • 정방향과 역방향 모두 되야 한다. → 캔슬 지원 가능
      • UIPercentDrivenInteractiveTransition이라는 구체 클래스도 제공한다.
        • 서브클래싱 가능. 할 때 메소드들의 super호출을 잊지 말 것
        • 제스처 핸들링 할때 상태 업데이트를 해주는 게 일반적이다.
      protocol UIViewControllerInteractiveTransitioning: NSObjectProtocol {
      	// animateTransition 대신 호출됨 
      	// 이 안에서 Animator를 호출하게 된다.
      	func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning)
      	optional var completionCurve: UIView.AnimationCurve { get }
      	optional var completionSpeed: CGFloat { get }
      }
      
    • 인터렉티브 프레젠테이션 단계

      • 애니매이션과 동일하지만 후에 InteractiveTransition 메소드를 호출함. 즉, 둘 다 구현해야 한다.
    • CollectionView 에 대한 특별한 레이아웃 지원: UICollectionViewTransitionLayout

      • 두 레이아웃 간의 차이를 보간해주는 레이아웃
      • transitionProgress 프로퍼티에 의해 동작하며, interactive한지는 구현자 마음
      • 서브클래싱 가능
        • 선형 이동 이상의 무언가를 보여주고 싶을 때
        • delegate에서 커스텀 객체를 반환해야 한다.
      • UICollectionView, delegate에 관련 API 추가
    • 트랜지션할 때 주의점

    • UIViewControllerTransitionCoordinator

      • 트랜지션은 캔슬이 가능하기 때문에, viewDidAppear가 viewWillAppear 뒤에 따라올 것으로 가정하면 안된다.
      • 그래서 현재 트랜지션 상태를 알 수 있는 객체를 트랜지션이 일어날 때, UIKit이 뷰컨트롤러에서 자동으로 할당해준다.
      • 트랜지션 사이의 애니매이션을 담당
        • 트랜지션 사이에서 트랜지션에 관여하지 않는 뷰의 애니매이션을 시킬 수 있다.
      • completionHandler 제공 → 모든 UINavigationController 트랜지션에 사용 가능
  • 자유도 줬다고 이상한 거 만들지 말아라. → 이 모든 건 사용자 경험을 향상 시키는 데 사용해야 한다.
  • 말로만으로는 이해가 잘 안될 수 있으니 예제코드