[번역] View Controller Programming Guide for iOS - Presentations and Transitions - Customizing the Transition Animations

Customizing the Transition Animations

원문

미리 요약

  • Transition Animation 커스터마이징은 많은 객체를 사용한다.
  • Transitioning Delegate는 트랜지션 애니메이션과 커스텀 프레젠테이션의 시작점이다.
    • UIViewControllerTransitioningDelegate 프로토콜을 채택하는 객체를 말한다.
      • 애니메이터 / 인터랙티브 애니메이터 / 프레젠테이션 컨트롤러를 제공한다.
      • 반드시 애니메이터나 인터랙티브 애니메이터를 제공할 필요는 없으며, 제공하지 않는다면 내장된 것을 사용한다.
      • modalPresentationStyle.custom일 때만 프레젠테이션 컨트롤러를 사용한다.
    • 애니메이션이 끝날 때 컨텍스트 트랜지셔닝 객체의 completeTransition(_:) 메소드를 호출해 주어야 한다.
  • Transitioning Context Object는 트랜지션에 포함되는 뷰 컨트롤러와 뷰, 컨테이너 뷰, 시작 및 끝 프레임 등에 관한 정보를 가지고 있다.
    • UIViewControllerContextTransitioning 프로토콜을 채택하는 객체를 말한다.
  • Transition Coordinator는 뷰 계층의 변화를 감지하고 이에 따라 컨텐츠를 애니메이팅할 수 있게 해준다.
    • UIViewControllerTransitionCoordinator 프로토콜을 채택하는 객체를 말한다.
    • 트랜지션이 일어나는 동안에만 존재한다.
  • 애니메이터 객체를 만들기 위해 다음의 절차를 따른다.
    1. 트랜지션에 포함된 관련 객체들을 얻는다.
    2. 애니메이션을 만든다.
    3. 트랜지션을 정리하고 완료한다.
  • 인터랙티브 트랜지션을 제공하고 싶다면 인터랙티브 트랜지션 객체를 만들어 제공하면 된다.
    • 인터랙티브 트랜지션은 일반적으로 UIPercentDrivenInteractiveInteraction 클래스를 사용한다.

트랜지션 애니메이션은 인터페이스에 변화에 대한 시각적 피드백을 제공한다. UIKit은 뷰 컨트롤러를 프레젠트하는 동안 사용하는 표준 트랜지션 스타일 집합을 제공하며, 커스텀 트랜지션을 적용하여 표준 트랜지션을 보충할 수 있다.

The Transition Animation Sequence

트랜지션 애니메이션은 한 뷰 컨트롤러의 컨텐츠와 다른 뷰 컨트롤러의 컨텐츠를 바꾼다. 트랜지션의 타입은 두 개, 프레젠테이션presentation과 디스미설dismissal이다. 프레젠테이션 트랜지션은 새로운 뷰 컨트롤러를 뷰 컨트롤러 계층에 추가하는 반면, 디스미설 트랜지션은 계층에서 한 개 이상의 뷰 컨트롤러를 제거한다.

트랜지션 애니메이션을 구현하기 위해 많은 객체를 사용한다. UIKit은 트랜지션에 사용되는 모든 객체에 대한 디폴트 버전을 제공하며, 그 모두나 일부분을 커스터마이징할 수 있다. 올바른 객체 집합을 선택한다면, 적은 양의 코드를 작성하고 애니메이션을 만들 수 있어야 한다. 인터랙션을 포함하는 애니메이션조차도 UIKit이 제공하는 존재하는 코드의 이점을 취한다면 쉽게 구현할 수 있다.

The Transitioning Delegate

트랜지셔닝 델리게이트는 트랜지션 애니메이션과 커스텀 프레젠테이션의 시작점이다. 트랜지셔닝 델리게이트는 UIViewControllerTransitioningDelegate 프로토콜을 준수하는 객체다. 이것의 임무는 UIKit에게 다음의 객체들을 제공하는 것이다.

  • 애니메이셔 객체. 애니메이터 객체는 뷰 컨트롤러의 뷰를 드러내고 숨기기 위해 사용되는 애니메이션을 만드는 책임이 있다. .트랜지셔닝 델리게이트는 프레젠트하는 뷰 컨트롤러와 디스미스하는 뷰 컨트롤러를 위해 개별 애니메이터 객체를 제공할 수 있다. 애니메이터 객체는 UIViewControllerAnimatedTransitioning 프로토콜을 준수한다.

  • 인터랙티브 애니메이터 객체. 인터랙티브 애니메이터 객체는 터치 이벤트나 제스처 레코그나이저를 사용하여 커스텀 애니메이션의 타이밍을 조절한다. 인터랙티브 애니메이터 객체는 UIViewControllerInteractiveTransitioning 프로토콜을 준수한다.

    인터랙티브 애니메이터를 만들기 위한 가장 쉬운 방법은 UIPercentDrivenInteractiveTransition 클래스를 서브클래싱하고 이벤트를 처리하는 코드를 추가하는 것이다. 클래스는 존재하는 애니메이터 객체를 사용하기 위해 만들어진 애니메이션의 타이밍을 제어한다. 인터랙티브 애니메이터를 직접 만든다면, 당신이 애니메이션의 각 프레임을 반드시 렌더링해야 한다.

  • 프레젠테이션 컨트롤러. 프레젠테이션 컨트롤러는 뷰 컨트롤러가 화면에 올라오는 동안 프레젠테이션 스타일을 관리한다. 시스템은 내장된 프레젠테이션 스타일에 대한 프레젠테이션 컨트롤러를 제공하며, 커스텀 프레젠테이션 스타일에 대한 커스텀 프레젠테이션 컨트롤러를 제공할 수 있다. Creating Custom Presentations에서 커스텀 프레젠테이션 컨트롤러를 만드는 더 많은 정보를 확인하라..

트랜지셔닝 델리게이트를 뷰 컨트롤러의 transitioningDelegate 프로퍼티에 할당하여 UIKit에게 커스텀 트랜지션이나 프레젠테이션을 수행하기를 원한다고 알린다. 델리게이트는 어떤 객체를 제공할지에 대해 선택적일 수 있다. 애니메이터 객체를 제공하지 않는다면 UIKit은 뷰 컨트롤러의 modalTransitionStyle 프로퍼에 있는 표준 트랜지션 애니메이션을 사용한다.

프레젠테이션 컨트롤러는 뷰 컨트롤러의 modalPresentationStyle 프로퍼티가 UIModalPresentationStyle.custom으로 설정되었을 때만 사용된다.

Implementing the Transitioning Delegate에서 트랜지셔닝 델리게이트를 구현하는 방법에 대해 확인하라. UIViewControllerTransitioningDelegate 프로토콜 레퍼런스에서 트랜지셔닝 델리게이트 객체의 메소드에 대한 정보를 확인하라.

The Custom Animation Sequence

프레젠트되는 뷰 컨트롤러의 transitioningDelegate 프로퍼티가 유효한 객체를 가지고 있을 때, UIKit은 제공된 커스텀 애니메이터 객체를 사용하는 뷰 컨트롤러를 프레젠트한다. 프레젠테이션을 준비할 때 UIKit은 트랜지셔닝 델리게이트의 animationController(_:forPresented:presenting:source:) 메소드를 호출하여 커스텀 애니메이터 객체를 획득한다. 객체가 사용 가능하면 UIKit은 다음의 단계를 수행한다.

  1. UIKit은 트랜지셔닝 델리게이트의 interactionControllerForPresentation(using:) 메소드를 호출하여 인터랙티브 애니메이터 객체가 사용 가능한지 확인한다. 이 메소드가 nil을 반환한다면 UIKit은 유저 인터랙션이 없는 애니메이션을 수행한다.

  2. UIKit은 애니메이터 객체의 transitionDuration 프로퍼티를 호출하여 애니메이션 기간을 얻는다.

  3. UIKit은 애니메이션을 시작하기 위해 적절한 메소드를 호출한다.

    • 인터랙티브하지 않은 애니메이션에 대하여, UIKit은 애니메이터 객체의 animateTransition(using:) 메소드를 호출한다.
    • 인터랙티브 애니메이션에 대하여, UIKit은 인터랙티브 애니메이터 객체의 startInteractiveTransition(_:) 메소드를 호출한다.
  4. UIKit은 애니메이터 객체가 컨텍스트 트랜지셔닝 객체의 completeTransition(_:) 메소드를 호출하기를 기다린다.

    커스텀 애니메이터는 애니메이션이 끝난 후 이 메소드를 호출하며, 일반적으로 애니메이션의 컴플리션 블록에서 호출한다. 이 메소드를 호출하여 트랜지션을 끝내며, UIKit에게 present(_:animated:completion:) 메소드의 컴플리션 핸들러와 애니메이터 객체의 animationEnded(_:) 메소드를 호출할 수 있게 되었다고 알릴 수 있게 된다.

뷰 컨트롤러를 디스미스할 때 UIKit은 트랜지셔닝 델리게이트의 animationController(forDismissed:) 메소드를 호출하고 다음의 단계를 수행한다.

  1. UIKit은 트랜지셔닝 델리게이트의 interactionControllerForDismissal(using:) 메소드를 호출하여 인터랙티브 애니메이터 객체가 사용 가능한지 확인한다. 이 메소드가 nil을 반환한다면 UIKit은 유저 인터랙션이 없는 애니메이션을 수행한다.

  2. UIKit은 애니메이터 객체의 transitionDuration 프로퍼티를 호출하여 애니메이션 기간을 얻는다.

  3. UIKit은 애니메이션을 시작하기 위해 적절한 메소드를 호출한다.

    • 인터랙티브하지 않은 애니메이션에 대하여, UIKit은 애니메이션 객체의 animateTransition(using:) 메소드를 호출한다.
    • 인터랙티브 애니메이션에 대하여, UIKit은 애니메이션 객체의 startInteractiveTransition(_:) 메소드를 호출한다.
  4. UIKit은 애니메이터 객체가 컨텍스트 트랜지셔닝 객체의 completeTransition(_:) 메소드를 호출하기를 기다린다.

    커스텀 애니메이터는 애니메이션이 끝난 후 이 메소드를 호출하며, 일반적으로 애니메이션의 컴플리션 블록에서 호출한다. 이 메소드를 호출하여 트랜지션을 끝내며, UIKit에게 present(_:animated:completion:) 메소드의 컴플리션 핸들러와 애니메이터 객체의 animationEnded(_:) 메소드를 호출할 수 있게 되었다고 알릴 수 있게 된다.

중요 : 애니메이션의 끝에 completeTransition(_:) 메소드를 호출하는 것은 필수적이다. UIKit은 트랜지션 프로세스를 종료하지 않으므로, 그 메소드를 호출할 때까지 앱에 제어권을 반환한다.

The Transitioning Context Object

트랜지션 애니메이션이 시작하기 전, UIKit은 트랜지셔닝 컨텍스트 객체를 만들고 애니메이션이 어떻게 수행되어야 하는지에 대한 정보로 이를 채운다. 트랜지셔닝 컨텍스트 객체는 코드에서 중요한 부분이 된다. 이는 UIViewControllerContextTransitioning 프로토콜을 구현하며 트랜지션에 포함되는 뷰 컨트롤러들과 뷰들에 대한 참조를 저장한다. 또한 애니메이션이 인터랙티브한지에 대한 것과 더불어, 어떻게 트랜지션을 수행해야 하는지에 대한 정보도 저장한다. 애니메이터 객체는 실제 애니메이션을 설정하고 수행하기 위해 이 모든 정보를 필요로 한다.

중요 : 커스텀 애니메이션을 설정할 때, 직접 관리하는 캐싱된 정보 대신에 항상 트랜지셔닝 컨텍스트 객체에 있는 객체와 데이터를 사용하라. 트랜지션은 다양한 조건에서 발생할 수 있으며, 몇몇은 애니메이션 파라미터를 변경할 수 있다. 트랜지셔닝 컨텍스트 객체는 애니메이션을 수행하기 위해 필요로 하는 올바른 정보를 가지고 있음을 보장하는 반면, 직접 캐싱한 정보는 애니메이터 메소드가 호출되는 때 무렵에는 오래되어 있을 수 있다.

애니메이터 객체는 animateTransition(using:) 메소드에 있는 객체를 받는다. 애니메이션은 제공된 컨테이너 뷰에서 발생해야 한다. 예를 들어 뷰 컨트롤러가 프레젠트될 때 뷰를 컨테이너 뷰의 서브뷰로 추가하라. 컨테이너 뷰는 윈도우나 일반적인 뷰일 수 있으나, 항상 애니메이션을 실행하기 위해 구성되어야 한다.

UIViewControllerContextTransitioning 프로토콜 레퍼런스에서 트랜지셔닝 컨텍스트 객체에 대한 더 많은 정보를 확인하라.

The Transition Coordinator

내장 트랜지션과 커스텀 트랜지션 모두에 대하여, UIKit은 수행할 필요가 있을 수 있는 여분의 애니메이션을 촉진시키기 위해 트랜지션 코디네이터 객체를 만든다. 뷰 컨트롤러의 프레젠테이션과 디스미설을 벗어나, 트랜지션은 인터페이스 회전이 발생하거나 뷰 컨트롤러의 프레임이 바뀌었을 때 발생할 수 있다. 이러한 모든 트랜지션들은 뷰 계층의 변화를 나타낸다. 트랜지션 코디네이터는 이러한 변화를 추적하고 같은 시간에 컨텐츠를 애니메이팅하는 방법이다. 트랜지션 코디네이터에 접근하기 위해 영향을 받는 뷰 컨트롤러의 transitionCoordinator 프로퍼티에서 객체를 얻는다. 트랜지션 코디네이터는 트랜지션 기간 동안에만 존재한다.

트랜지션에 대한 정보를 얻기 위해, 트랜지션 애니메이션이 일어나는 것과 함께 수행되기 원하는 애니메이션 블록을 등록하기 위해 트랜지션 코디네이터를 사용하라. 트랜지션 코디네이터는 UIViewControllerTransitionCoordinatorContext 프로토콜을 준수하는 객체이며, 이는 타이밍 정보, 애니메이션의 현재 상태에 대한 정보, 트랜지션에 포함된 뷰와 뷰 컨트롤러들을 제공한다. 애니메이션 블록이 수행될 때, 또한 같은 정보를 갖는 컨텍스트 객체를 받는다.

UIViewControllerTransitionCoordinator 프로토콜 레퍼런스에서 트랜지션 코디네이터 객체에 대한 더 많은 정보를 확인하라. UIViewControllerTransitionCoordinatorContext 프로토콜 레퍼런스에서 애니메이션을 구성하기 위해 사용할 수 있는 컨텍스트 관련 정보를 확인하라.

Presenting a View Controller Using Custom Animations

커스텀 애니메이션을 사용하여 뷰 컨트롤러를 프레젠트할 때, 존재하는 뷰 컨트롤러의 액션 메소드를 따라서 수행하라.

  1. 프레젠트하기 원하는 뷰 컨트롤러를 만든다.
  2. 커스텀 트랜지셔닝 델리게이트 객체를 만들고 뷰 컨트롤러의 transitioningDelegate 프로퍼티에 할당한다. 트랜지셔닝 델리게이트의 메소드들은 요청받을 때 커스텀 애니메이터 객체를 만들고 반환할 수 있어야 한다.
  3. 뷰 컨트롤러를 프레젠트하기 위해 present(_:animated:completion:) 메소드를 호출한다.

present(_:animated:completion:) 메소드를 호출할 때, UIKit은 프레젠테이션 프로세스를 개시한다. 프레젠테이션은 다음 런 루프 반복 동안 시작하며, 커스텀 애니메이터가 completeTransition(_:) 메소드를 호출할 때까지 지속한다. 인터랙티브 트랜지션은 트랜지션이 진행 중인 동안에 터치 이벤트를 처리할 수 있게 해준다. 하지만 인터랙티브하지 않은 트랜지션은 애니메이터 객체가 지정한 기간 동안 실행된다.

Implementing the Transitioning Delegate

트랜지셔닝 델리게이트의 목적은 커스텀 객체를 만들고 반환하는 것이다. 실제 작업의 대부분은 애니메이터 객체 자체에서 처리된다.

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let animator = MyAnimator()
return animator
}

트랜지셔닝 델리게이트의 다른 메소드들은 이전 예제처럼 단순하게 구현될 수 있다. 앱의 현재 상태에 기반하여 다른 애니메이터 객체를 반환하기 위한 커스텀 로직을 작성할 수도 있다. UIViewControllerTransitioningDelegate 프로토콜 레퍼런스에서 트랜지셔닝 델리게이트의 메소드들에 관한 더 많은 정보를 확인하라.

Implementing Your Animator Objects

애니메이터 객체는 UIViewControllerAnimatedTransitioning 프로토콜을 채택하는 객체다. 애니메이터 객체는 일정 시간 동안 실행되는 애니메이션을 만든다. 애니메이터 객체의 키는 animateTransition(using:) 메소드다. 실제 애니메이션을 만들기 위해 이 메소드를 사용한다. 애니메이션 프로세스는 대략 다음의 구획들로 나누어진다.

  1. 애니메이션 파라미터를 얻는다.
  2. Core Animation이나 UIView 애니메이션 메소드를 사용하여 애니메이션을 만든다.
  3. 트랜지션을 정리하고 완료한다.

Getting the Animation Parameters

animateTransition(_:) 메소드에 넘겨진 컨텍스트 트랜지셔닝 객체는 애니메이션을 수행할 때 사용하는 데이터를 포함한다. 절대 직접 캐싱한 정보를 사용하지 마라. 컨텍스트 트랜지셔닝 객체에서 최신 데이터를 얻을 수 있을 때 뷰 컨트롤러에서 정보를 가져와라. 뷰 컨트롤러를 프레젠트하고 디스미스하는 것은 가끔 뷰컨트롤러 외부 객체를 포함한다. 예를 들어 커스텀 프레젠테이션 컨트롤러는 프레젠테이션의 일부분으로서 백그라운드 뷰를 추가할 수 있을 것이다. 컨텍스트 트랜지셔닝 객체는 여분의 뷰와 객체를 취하고, 애니메이팅하기 올바른 뷰와 함께 제공한다.

  • 트랜지션에 포함된 “from” 뷰 컨트롤러와 “to” 뷰 컨트롤러를 얻기 위해 viewController(forKey:) 메소드를 두 번 호출하라. 트랜지션에 참여하는 뷰 컨트롤러가 무엇인지 안다고 절대 가정하지 마라. UIKit은 새로운 trait 환경이나 앱에서의 요청에 대한 응답에 적응하는 동안 뷰 컨트롤러를 변경할 수 있다.
  • containerView 프로퍼티를 호출하여 애니메이션을 위한 슈퍼뷰를 얻는다. 이 뷰에 모든 주요 서브 뷰들을 추가한다. 예를 들어 프레젠테이션 동안 프레젠트되는 뷰 컨트롤러의 뷰를 이 뷰에 추가한다.
  • view(forKey:) 메소드를 호출하여 추가되거나 제거되는 뷰를 얻는다. 뷰 컨트롤러의 뷰는 트랜지션 동안 추가되거나 제거되는 유일한 것이 아닐 수 있다. 프레젠테이션 컨트롤러는 또한 반드시 추가되거나 제거되는 뷰를 계층에 삽입할 수 있다. view(forKey:) 메소드는 추가하거나 제거할 필요가 있는 모든 것을 포함하는 루트 뷰를 반환한다.
  • finalFrame(for:) 메소드를 호출하여 추가되거나 제거되려는 뷰에 대한 최종 프레임 직사각형을 얻는다.

컨텍스트 트랜지셔닝 객체는 트랜지션에 포함된 뷰 컨트롤러, 뷰, 프레임 직사각형을 식별하기 위해 “from”과 “to”라는 용어를 사용한다. “from” 뷰 컨트롤러는 항상 트랜지션의 시작에서 화면에 나타나 있는 뷰를 말한다. “to” 뷰 컨트롤러는 트랜지션의 끝에서 보이게 될 뷰를 말한다. “from” 및 “to” 뷰 컨트롤러는 프레젠테이션과 디스미설 사이에서 위치를 서로 바꾼다.

값을 바꾸는 것은 프레젠테이션과 디스미설 모두를 처리하는 하나의 애니메이터를 작성하기 쉽게 해준다. 애니메이터를 설계할 때 이것이 프레젠테이션을 애니메이팅하는지, 디스미설을 애니메이팅하는지 알게 하기 위한 프로퍼티를 추가하는 것이 해야 할 전부다. 둘 사이의 유일한 요구 조건의 차이는 다음과 같다.

  • 프레젠테이션에서, 컨테이너 뷰 계층에 “to” 뷰를 추가한다.
  • 디스미설에서, 컨테이너 뷰 계층에서 “from” 뷰를 제거한다.

Creating the Transition Animations

전형적인 프레젠테이션 동안 프레젠트되는 뷰 컨트롤러에 속한 뷰는 애니메이션된다. 다른 뷰는 프레젠테이션의 일부분으로서 애니메이션될 수 있으나, 애니메이션의 주요 타겟은 항상 뷰 계층에 추가되려는 뷰다.

메인 뷰를 애니메이팅할 때 애니메이션을 구성하기 위해 취하는 기본 액션은 동일하다. 트랜지셔닝 컨텍스트 객체로부터 필요한 객체와 데이터를 꺼내오고, 실제 애니메이션을 만들기 위해 이 정보를 사용한다.

  • 프레젠테이션 애니메이션

    • viewController(forKey:)view(forKey) 메소드를 사용하여 트랜지션에 포함된 뷰 컨트롤러와 뷰를 받는다.
    • “to” 뷰의 시작 위치를 설정한다. 또한 다른 프로퍼티들도 시작 값으로 설정한다.
    • 컨텍스트 트랜지셔닝 컨텍스트의 finalFrame(for:) 메소드에서 “to” 뷰의 마지막 위치를 얻는다.
    • “to” 뷰를 컨테이너 뷰의 서브뷰로 추가한다.
    • 애니메이션을 만든다.
      • 애니메이션 블록에서 컨테이너에 있는 “to” 뷰를 최종 위치로 애니메이팅한다. 또한 다른 프로퍼티들도 최종 값으로 설정한다.
      • 컴플리션 블록에서, completeTransition(_:) 메소드를 호출하고, 다른 클린업 작업을 수행한다.
  • 디스미설 애니메이션

    • viewControler(forKey:)view(forKey:) 메소드를 사용하여 트랜지션에 포함된 뷰 컨트롤러와 뷰를 받는다.

    • “from” 뷰의 마지막 위치를 계산한다. 이 뷰는 이제 디스미스되려고 하는 프레젠트되는 뷰 컨트롤러에 속한다.

    • “to” 뷰를 컨테이너 뷰의 서브뷰로 추가한다.

      프레젠테이션 동안 프레젠트하는 뷰 컨트롤러에 속한 뷰는 트랜지션이 완료될 때 제거된다. 결과적으로 디스미설 작업 동안 뷰를 컨테이너에 다시 추가해야 한다.

    • 애니메이션을 만든다.

      • 애니메이션 블록에서 “from” 뷰를 컨테이너 뷰의 최종 위치로 애니메이팅한다. 또한 다른 프로퍼티들도 최종 값으로 설정한다.
      • 컴플리션 블록에서, “from” 뷰를 뷰 계층에서 제거하고 completeTransition(_:) 메소드를 호출한다. 필요하다면 다른 클린업 작업도 수행한다.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let fromVC = transitionContext.viewController(forKey: .from)!
let toVC = transitionContext.viewController(forKey: .to)!
let toView = transitionContext.view(forKey: .to)!
let fromView = transitionContext.view(forKey: .from)!

let containerFrame = containerView.frame
var toViewStartFrame = transitionContext.initialFrame(for: toVC)
let toViewFinalFrame = transitionContext.finalFrame(for: toVC)
var fromViewFinalFrame = transitionContext.finalFrame(for: fromVC)

if presenting {
toViewStartFrame.origin.x = containerFrame.size.width
toViewStartFrame.origin.y = containerFrame.size.height
} else {
fromViewFinalFrame = CGRect(x: containerFrame.size.width, y: containerFrame.size.height, width: toView.frame.size.width, height: toView.frame.size.height)
}

containerView.addSubview(toView)
toView.frame = toViewStartFrame

UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
if self.presenting {
toView.frame = toViewFinalFrame
} else {
fromView.frame = fromViewFinalFrame
}
}, completion: { finished in
let success = transitionContext.transitionWasCancelled
if (self.presenting && !success) || (!self.presenting && success) {
toView.removeFromSuperview()
} else {
transitionContext.completeTransition(success)
}
})
}

Cleaning Up After the Animations

트랜지션 애니메이션이 끝나고, completeTransition(_:) 메소드를 호출하는 것은 중요하다. 이 메소드를 호출하여 UIKit에게 트랜지션이 완료하였으며 유저가 프레젠트되는 뷰 컨트롤러를 사용할 수 있게 되었다고 알린다. 이 메소드를 호출하는 것은 또한 present(_:animated:completion:)과 애니메이터 객체의 animationEnded(_:) 메소드를 포함한 다른 컴플리션 핸들러의 실행을 트리거한다. completeTransition(_:) 메소드를 호출하기 위한 최상의 장소는 애니메이션 블록의 컴플리션 핸들러다.

트랜지션은 취소될 수 있기 때문에 컨텍스트 객체의 transitionWasCancelled 프로퍼티의 값을 사용하여 클린업이 필요한지를 결정해야 한다. 프레젠테이션이 취소될 때 애니메이터는 반드시 뷰 계층에서 만든 변경들을 되돌려야 한다. 성공적인 디스미설도 비슷한 액션을 요구한다.

Adding Interactivity to Your Transitions

애니메이션을 인터랙티브하게 만드는 가장 쉬운 방법은 UIPercentDrivenInteractiveTransition 객체를 사용하는 것이다. UIPercentDrivenInteractiveTransition 객체는 존재하는 애니메이터 객체와 함께 동작하여 애니메이션의 타이밍을 제어한다. 제공하는 완료 퍼센트 값을 사용하여 타이밍 제어 작업을 수행한다. 완료 퍼센트 값을 계산하고 각각의 새로운 이벤트가 도착했을 때 이를 갱신하기 위해, 필요하다면 이벤트 처리 코드를 만들어 놓는 것만 하면 된다.

UIPercentDrivenInteractiveTransition 클래스는 서브클래싱하여 사용해도 되고, 서브클래싱하지 않고 사용해도 된다. 서브클래싱한다면, 이벤트 처리 코드를 한 번 설정하기 위해 이니셜라이저 또는 startInteractiveTransition(_:) 메소드를 사용한다. 이후에 새로운 완료 퍼센트 값을 계산하기 위해 커스텀 이벤트 처리 코드를 사용하고 updateInteractiveTransition(_:) 메소드를 호출한다. 트랜지션이 완료되어야 한다고 결정했을 때 finishInteractiveTransition() 메소드를 호출한다.

func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
contextData = transitionContext
panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleSwipeUpdate))
panGesture.maximumNumberOfTouches = 1

let container = transitionContext.containerView
container.addGestureRecognizer(panGesture)
}

제스처 레코그나이저는 도착한 각각의 새로운 이벤트에 대하여 액션 메소드를 호출한다. 액션 메소드의 구현은 제스처 레코그나이저의 상태 정볼르 사용하여 제스처가 성공인지, 실패인지, 여전히 진행 중인지 결정한다. 같은 시간에 제스처에 대한 새로운 퍼센트 값을 계산하기 위해 마지막 터치 이벤트 정보를 사용할 수 있다.

@objc func handleSwipeGesture(_ gestureRecognizer: UITapGestureRecognizer) {
let container = contextData.containerView
if gestureRecognizer.state == .began {
panGesture.setTranslation(CGPoint(x: 0, y: 0), in: container)
} else if gestureRecognizer.state == .changed {
let translation = panGesture.translation(in: container)
let percentage = fabs(translation.y / container.bounds.height)
updateInteractiveTransition(percentage)
} else {
finishInteractiveTransition()
contextData.containerView.removeGestureRecognizer(panGesture)
}
}

알아두기 : 계산한 값은 애니메이션의 전체 길이에 대한 완료 퍼센트를 나타낸다. 인터랙티브 애니메이션에 대하여, 초기 속력, 댐핑 값, 비선형 완료 커브와 같은 비선형 효과를 피하기를 원할 수 있다. 그러한 효과들은 이벤트의 터치 위치와 밑에 깔린 뷰들의 움직임을 분리하는 경향이 있다.

Creating Animations that Run Alongside a Transition

트랜지션에 포함된 뷰 컨트롤러는 프레젠테이션이나 트랜지션 애니메이션의 상단에서 추가적인 애니메이션을 수행할 수 있다. 예를 들어 프레젠트되는 뷰 컨트롤러는 트랜지션 동안 자신의 뷰 계층을 애니메이팅할 수 있고, 트랜지션이 발생하는 동안 모션 효과나 다른 시각적 피드백을 추가할 수 있다. 프레젠트되는 뷰 컨트롤러나 프레젠트하는 뷰 컨트롤러의 transitionCoordinator 프로퍼티에 접근이 가능한 한, 어떠한 객체라도 애니메이션을 만들 수 있다. 트랜지션 코디네이터는 오직 트랜지션이 진행 중일 때만 존재한다.

애니메이션을 만들기 위해 트랜지션 코디네이터의 animate(alongSideTransition:completion:) 또는 animateAlongSideTransition(in:animation:completion:) 메소드를 호출한다. 제공한 블록은 트랜지션 애니메이션이 시작할 때까지 저장되며, 나머지 트랜지션 애니메이션을 따라 실행된다.

Using a Presentation Controller with Your Animations

커스텀 프레젠테이션에 대하여, 프레젠트되는 뷰 컨트롤러에 커스텀 외관을 주기 위해 커스텀 프레젠테이션 컨트롤러를 제공할 수 있다. 프레젠테이션 컨트롤러는 뷰 컨트롤러와 컨텐츠와 분리된 커스텀 크롬을 관리한다. 예를 들어 뷰 컨트롤러의 뷰 뒤에 놓여진 흐릿한 뷰는 프레젠테이션 컨트롤러가 관리할 수 있다. 특정 뷰 컨트롤러의 뷰를 관리하지 않는다는 사실은 어떠한 뷰 컨트롤러에 대해서도 같은 프레젠테이션 컨트롤러를 사용할 수 있다는 것을 의미한다.

프레젠트되는 뷰 컨트롤러의 트랜지셔닝 델리게이트에 커스텀 프레젠테이션 컨트롤러를 제공한다. (뷰 컨트롤러의 modalPresentationStyle은 반드시 UIModalPresentationStyle.custom이어야 한다.) 프레젠테이션 컨트롤러는 애니메이터 객체와 병렬적으로 동작한다. 애니메이터 객체가 뷰 컨트롤러의 뷰를 애니메이팅할 때, 프레젠테이션 컨트롤러는 추가적인 뷰들을 애니메이팅한다. 트랜지션의 끝에서 프레젠테이션 컨트롤러는 뷰 계층에 최종 조절을 수행할 수 있는 기회를 갖는다.

Creating Custom Presentations에서 커스텀 프레젠테이션 컨트롤러를 만드는 방법에 대해 확인하라.