[번역] View Controller Programming Guide for iOS - Presentations and Transitions - Creating Custom Presentations

원문

미리 요약

  • 프레젠테이션 컨트롤러는 뷰 컨트롤러의 프레젠테이션 및 디스미설 시의 스타일을 커스터마이징하기 위해 사용한다.
    • UIPresentationController 클래스를 서브클래싱하여 사용한다.

UIKit은 뷰 컨트롤러의 컨텐츠를 그 컨텐츠가 프레젠트되고 화면에 표시되는 방법과 분리한다. 프레젠트되는 뷰 컨트롤러는 밑에 깔려 있는 프레젠테이션 컨트롤러 객체가 관리하며, 이는 뷰 컨트롤러의 뷰를 표시하기 위해 사용되는 시각적 스타일을 관리한다. 프레젠테이션 컨트롤러는 다음의 것들을 할 수 있다.

  • 프레젠트되는 뷰 컨트롤러의 크기를 설정한다.
  • 프레젠트되는 컨텐츠의 시각적 외관을 변경하기 위해 커스텀 뷰를 추가한다.
  • 커스텀 뷰에 트랜지션 애니메이션을 제공한다.
  • 앱의 환경이 변화할 때 프레젠테이션의 시각적 외관을 조절한다.

UIKit은 표준 프레젠테이션 스타일에 대한 프레젠테이션 컨트롤러를 제공한다. 뷰 컨트롤러의 프레젠테이션 스타일을 UIModalPresentationStyle.custom으로 설정하고 적절한 트랜지셔닝 델리게이트를 제공하면 UIKit은 대신에 커스텀 프레젠테이션 컨트롤러를 사용한다.

The Custom Presentation Process

프레젠테이션 스타일이 UIModalPresentationStyle.custom인 뷰 컨트롤러를 프레젠트할 때, UIKit은 프레젠테이션 프로세스를 관리하기 위해 커스텀 프레젠테이션 컨트롤러를 찾는다. 프레젠테이션 프로세스 동안 UIKit은 프레젠테이션 컨트롤러의 메소드들을 호출하여 커스텀 뷰를 설정하고 애니메이팅할 기회를 제공한다.

프레젠테이션 컨트롤러는 전반적인 트랜지션을 구현하는 애니메이터 객체를 따라 동작한다. 애니메이터 객체는 뷰 컨트롤러의 컨텐츠를 화면에 애니메이팅하고, 프레젠테이션 컨트롤러는 다른 모든 것들을 처리한다. 일반적으로 프레젠테이션 컨트롤러는 그 자체의 뷰를 애니메이팅한다. 하지만 프레젠테이션 컨트롤러의 presentedView 프로퍼티를 재정의하여 애니메이터 객체가 그러한 뷰들 모두나 일부를 애니메이팅하도록 할 수 있다.

프레젠테이션 동안 UIKit은 다음의 작업을 한다.

  1. 트랜지셔닝 델리게이트의 presentationController(forPresented:presenting:source:) 메소드를 호출하여 커스텀 프레젠테이션 컨트롤러를 얻는다.
  2. 트랜지셔닝 델리게이트에게 애니메이터와 인터랙티브 애니메이터를 요청한다.
  3. 프레젠테이션 컨트롤러의 presentationTransitionWillBegin() 메소드를 호출한다. 이 메소드의 구현은 뷰 계층에 커스텀 뷰를 추가하고, 그 뷰에 대한 애니메이션을 구성해야 할 것이다.
  4. 프레젠테이션 컨트롤러에서 presentedView를 얻는다. 이 뷰는 애니메이터 객체에 의해 제 위치로 애니메이팅된다. 일반적으로 이 메소드는 프레젠트되는 뷰 컨트롤러의 루트 뷰를 반환한다. 프레젠테이션 컨트롤러는 필요하다면 커스텀 백그라운드 뷰로 해당 뷰를 교체할 수 있다. 다른 뷰를 지정한다면 프레젠트되는 뷰 컨트롤러의 루트 뷰를 뷰 계층에 반드시 임베드해야 한다.
  5. 트랜지션 애니메이션을 수행한다.

디스미설 동안 UIKit은 다음의 작업을 한다.

  1. 현재 보여지고 있는 뷰 컨트롤러에서 커스텀 프레젠테이션 컨트롤러를 얻는다.

  2. 트랜지셔닝 델리게이트에게 애니메이터와 인터랙티브 애니메이터를 요청한다.

  3. 프레젠테이션 컨트롤러의 dismissalTransitionWillBegin() 메소드를 호출한다. 이 메소드의 구현은 뷰 계층에 커스텀 뷰를 추가하고, 그 뷰에 대한 애니메이션을 구성해야 할 것이다.

  4. 프레젠테이션 컨트롤러의 presentedView를 얻는다.

  5. 트랜지션 애니메이션을 수행한다.

    트랜지션 애니메이션은 애니메이터 객체가 만든 메인 애니메이션과, 메인 애니메이션을 따라 실행되기 위해 구성된 다른 애니메이션들을 포함한다. The Transition Animation Sequence에서 트랜지션 애니메이션에 대한 정보를 확인하라.

    애니메이션 프로세스 동안 UIKit은 프레젠테이션 컨트롤러의 containerViewWillLayoutSubviews()containerViewDidLayoutSubviews() 메소드를 호출하여 커스텀 제약들을 제거할 수 있게 해준다.

  6. 트랜지션 애니메이션이 끝날 때 dismissalTransitionDidEnd(_:) 메소드를 호출한다.

프레젠테이션 프로세스 동안 프레젠테이션 컨트롤러의 frameOfPresentedViewInContainerViewpresentedView 프로퍼티는 여러 번 호출될 수 있다. 그러므로 커스텀 구현은 빠르게 값을 반환해야 한다. 또한 presentedView 프로퍼티의 구현은 뷰 계층을 설정하려고 하지 않아야 한다. 뷰 계층은 이미 그 프로퍼티가 호출되는 시점에 구성되어 있어야 한다.

Creating a Custom Presentation Controller

커스텀 프레젠테이션 스타일을 구현하기 위해 UIPresentationController를 서브클래싱하고 프레젠테이션을 위한 뷰와 애니메이션을 만들기 위해 코드를 추가한다. 커스텀 프레젠테이션 컨트롤러를 만들 때 다음의 질문을 고려하라.

  • 추가하고 싶은 뷰는 무엇인가?
  • 화면에 나타나는 추가 뷰들을 어떻게 애니메이팅하고 싶은가?
  • 프레젠트되는 뷰 컨트롤러의 크기는 무엇이어야 하는가?
  • 프레젠테이션은 어떻게 수평 레귤러 사이즈 클래스와 수평 컴팩트 사이즈 클래스 사이를 조절해야 하는가?
  • 프레젠트하는 뷰 컨트롤러의 뷰는 프레젠테이션이 끝날 때 제거되어야 하는가?

이 질문들에 대한 모든 결정들은 UIPresentationController 클래스의 다른 메소드들을 재정의해야 할 필요가 있다.

Setting the Frame of the Presented View Controller

프레젠트되는 뷰 컨트롤러의 프레임 직사각형을 변경하여 사용 가능한 공간의 오직 일부분만 채우도록 할 수 있다. 기본적으로 프레젠트되는 뷰 컨트롤러는 컨테이너 뷰의 프레임을 완전하게 채우는 크기를 갖게 된다. 프레임 직사각형을 변경하려면 프레젠테이션 컨트롤러의 frameOfPresentedViewInContainerView 메소드를 재정의하라. 다음의 예제는 프레임이 컨테이너 뷰의 오른쪽 반만을 커버하도록 변경한다. 이 경우 프레젠테이션 컨트롤러는 컨테이너의 다른 절반을 커버하기 위해 흐릿한 백그라운드 뷰를 사용한다.

var frameOfPresentedViewInContainerView {
var presentedViewFrame = CGRect.zero
let containerBounds = containerView.bounds
presentedViewFrame.size = CGSize(width: floorf(containerBounds.size.width / 2), height: containerBounds.size.height)
presentedViewFrame.origin.x = containerBounds.size.width - presentedViewFrame.size.width
return presentedViewFrame
}

Managing and Animating Custom Views

커스텀 프레젠테이션은 종종 프레젠트되는 컨텐츠에 커스텀 뷰를 추가하는 것을 포함한다. 커스텀 뷰를 사용하여 시각적 장식을 추가하거나 프레젠테이션의 실용적인 동작을 추가하기 위해 구현하라. 예를 들어 백그라운드 뷰는 제스처 레코그나이저를 통합하여 프레젠트되는 컨텐츠의 경계 밖에서의 특정 액션을 추적할 수 있다.

프레젠테이션 컨트롤러는 프레젠테이션과 관련된 모든 커스텀 뷰를 만들고 관리할 책임이 있다. 보통 프레젠테이션 컨트롤러의 초기화 과정에서 커스텀 뷰를 만든다. 다음은 자체적으로 소유하는 흐릿한 뷰를 만드는 커스텀 뷰 컨트롤러의 초기화 메소드를 보여준다. 이 메소드는 뷰를 만들고 몇 가지 최소한의 구성을 수행한다.

init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
dimmingView = UIView()
dimmingView.backgroundColor = UIColor(white: 0, alpha: 0.4)
dimmingView.alpha = 0
}

presentationTransitionWillBegin() 메소드를 사용하여 커스텀 뷰를 화면에 애니메이팅한다. 이 메소드에서 커스텀 뷰를 구성하고 컨테이너 뷰에 추가한다. 애니메이션을 만들기 위해 프레젠트되는 뷰 컨트롤러나 프레젠트하는 뷰 컨트롤러의 트랜지션 코디네이터를 사용한다. 이 메소드에서 프레젠트되는 뷰 컨트롤러의 뷰를 변경하지 마라. 애니메이터 객체는 frameOfPresentedViewInContainerView 프로퍼티에서 반환한 프레임 직사각형 안에 있는 프레젠트되는 뷰 컨트롤러를 애니메이팅할 책임이 있다.

func presentationTransitionWillBegin() {
let containerView = containerView!
let presentedViewController = presentedViewController!

dimmingView.frame = containerView.bounds
dimmingView.alpha = 0

containerView.insertSubview(dimmingView, at: 0)

if let transitionCoordinator = presentedViewController.transitionCoordinator {
transitionCoordinator.animate(alongsideTransition: { context in
self.dimmingView.alpha = 1
}, completion: nil)
} else {
dimmingView.alpha = 1
}
}

프레젠테이션의 끝에서 presentationTransitionDidEnd(_:) 메소드를 사용하여 프레젠테이션의 취소가 불러일으키는 클린업 작업을 처리한다. 인터랙티브 애니메이터 객체는 쓰레스홀드 조건이 충족되지 않았다면 트랜지션을 취소할 수 있을 것이다. 이것이 일어날 때 UIKit은 presentationTransitionDidEnd(_:) 메소드를 false 값과 함게 호출한다. 취소가 발생할 때 프레젠테이션의 시작에서 추가한 커스텀 뷰들을 삭제하고, 다른 뷰들을 이전 구성으로 하여 반환한ㄷ.ㅏ

func presentationTransitionDidEnd(_ completed: Bool) {
if !completed {
dimmingView.removeFromSuperview()
}
}

뷰 컨트롤러가 디스미스될 때 dismissalTransitionDidEnd(_:) 메소드를 사용하여 뷰 계층에서 커스텀 뷰를 삭제한다. 뷰의 사라짐을 애니메이팅하고 싶다면 dismissalTransitionDidEnd(_:) 메소드에서 애니메이션을 설정한다. 디스미설이 성공했는지 취소되었는지 확인하기 위해 항상 dismissalTransitionDidEnd(_:) 메소드의 매개변수를 확인하라.

func dismissalTransitionWillBegin() {
guard let transitionCoordinator = presentedViewController?.transitionCoordinator else {
dimmingView.alpha = 1
return
}
transitionCoordinator.animate(alongsideTransition: { context in
self.dimmingView.alpha = 0
}, completion: nil)
}

func dismissalTransitionDidEnd(_ completed: Bool) {
if completed {
dimmingView.removeFromSuperview()
}
}

Vending Your Presentation Controller to UIKit

뷰 컨트롤러를 프레젠트할 때 커스텀 프레젠테이션 컨트롤러를 사용하여 표시하기 위해 다음의 지시를 따르라.

  • 프레젠트되는 뷰 컨트롤러의 modalPresentationStyle 프로퍼티를 UIModalPresentationStyle.custom으로 설정하라.
  • 프레젠트되는 뷰 컨트롤러의 transitioningDelegate 프로퍼티에 트랜지셔닝 델리게이트를 할당하라.
  • 트랜지셔닝 델리게이트의 presentationController(forPresented:presenting:source:) 메소드를 구현하라.

UIKit은 프레젠테이션 컨트롤러가 필요할 때 트랜지셔닝 델리게이트의 presentationController(forPresented:presenting:source:) 메소드를 호출한다. 이 메소드의 구현은 간단하다. 간단하게 프레젠테이션 컨트롤러를 만들고, 구성하고, 반환하면 된다. 이 메소드가 nil을 반환한다면 UIKit은 풀스크린 프레젠테이션 스타일을 사용하여 뷰 컨트롤러를 프레젠트한다.

func presentationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIPresentationController? {
let myPresentation = MyPresentation(presentedViewController: presented, presenting: presenting)
return myPresentation
}

Adapting to Different Size Classes

프레젠테이션이 화면에 나타나는 동안 UIKit은 trait나 컨테이너 뷰의 크기 변경이 일어날 때 프레젠테이션 컨트롤러에게 알린다. 이러한 변화는 일반적으로 디바이스 회전 동안 발생하나 다른 때에도 발생할 수 있다. 프레젠테이션의 커스텀 뷰를 조절하기 위해 trait 및 사이즈 알림을 사용할 수 있고, 적절하게 프레젠테이션 스타일을 갱신할 수 있다.

Building an Adaptive Interface에서 새로운 trait와 크기에 맞게 조절하는 방법에 대해 확인하라.