Presentations and Transitions

Panther·2021년 7월 21일
1

Presenting a View Controller

뷰 컨트롤러를 표시하는 방법은 두 가지가 있습니다. 컨테이너 뷰 컨트롤러에 끼워넣는 것 혹은 직접 제시하는 방법입니다. 컨테이너 뷰 컨트롤러는 앱의 주요 네비게이션을 제공합니다. 뷰 컨트롤러를 직접 제시하는 것 역시 중요한 네비게이션 도구입니다. 현재 뷰 컨트롤러의 상단에 새로운 뷰 컨트롤러를 표시하기 위해 직접 제시하는 것을 사용할 수 있습니다. 보통 모달 인터페이스를 구현하길 원할 때 뷰 컨트롤러를 제시합니다. 하지만 다른 목적을 위해서도 사용할 수 있습니다.

뷰 컨트롤러를 제시하기 위한 지원은 UIViewController 클래스에 내장되어 있으며, 모든 뷰 컨트롤러 객체에서 사용이 가능합니다. UIKit이 다른 뷰 컨트롤러에 요청을 다시 라우트하더라도, 어떠한 뷰 컨트롤러든 다른 뷰 컨트롤러로부터 제시될 수 있습니다. 뷰 컨트롤러를 제시하는 것은 뷰 컨트롤러를 제시할 기존 뷰 컨트롤러와 제시될 예정인 새로운 뷰 컨트롤러 사이의 관계를 생성합니다. 이 관계는 뷰 컨트롤러 계층구조의 일부분을 형성하고, 제시된 뷰 컨트롤러로부터 빠져나올 때까지 관계가 유지됩니다.

The Presentation and Transition Process

뷰 컨트롤러를 제시하는 것은 스크린에 새로운 컨텐트를 애니메이션할 수 있는 빠르고 쉬운 방법입니다. UIKit에 내장된 프리젠테이션 기계는 내장된 혹은 커스텀 애니메이션을 사용해 새로운 뷰 컨트롤러를 표시할 수 있도록. 해줍니다. 내장된 프리젠테이션과 애니메이션은 매우 적은 코드만을 요구할 것입니다. 왜냐하면 UIKit이 작동에 대한 모든 처리를 하기 때문입니다. 또한, 작은 노력을 들이고도 커스텀 프리젠테이션이나 애니메이션을 생성할 수 있으며, 이들을 모든 뷰 컨트롤러에 사용할 수 있습니다.

뷰 컨트롤러의 프리젠테이션은 코드 작성 혹은 세그를 사용해 초기화할 수 있습니다. 만약 설계 시 앱의 네비게이션을 알고 있다면, 세그는 프리젠테이션을 초기화하기 위한 가장 쉬운 방법입니다. 더 많은 동적 인터페이스 혹은 세그를 초기화하기 위한 전용 컨트롤이 없는 경우 뷰 컨트롤러를 제시하려면 UIViewController의 메소드를 사용하시기 바랍니다.

Presentation Styles

뷰 컨트롤러의 프리젠테이션 스타일은 스크린에 나타나는 모양을 제어합니다. UIKit은 많은 표준 프리젠테이션 스타일을 정의합니다. 이는 각각의 고유한 모양새와 의도를 갖고 있습니다. 커스텀 프리젠테이션 스타일을 정의할 수도 있습니다. 앱을 설계할 때 시도하고자 하는 바에 가장 일치하는 프리젠테이션 스타일을 선택하시기 바랍니다. 그리고 제시하고자 하는 뷰 컨트롤러의 modalPresentationStyle 속성에 적합한 값을 할당하시기 바랍니다.

Full-Screen Presentation Styles

전체 화면 프리젠테이션 스타일은 기본 컨텐트와 상호작용하는 것을 방지하면서 전체 스크린을 덮습니다. 수평 regular 환경에서 전체 스크린 스타일의 한 가지만이 기본 컨텐트를 완전히 덮습니다. 나머지는 흐리게 보이는 뷰 혹은 투명도를 기본 뷰 컨트롤러의 부분에서 보여질 수 있도록 통합합니다. 수평 compact 환경에서 전체 스크린 프리젠테이션은 자동으로 UIModalPresentationFullScreen 스타일에 적응하고, 기본 컨텐트 모드를 덮습니다.

Figure 8-1은 수평 regular 환경에서 UIModalPresentationFullScreen, UIModalPresentationPageSheet, UIModalPresentationFormSheet 스타일을 사용한 프리젠테이션의 모습을 보여주고 있습니다. 그림에서 왼쪽 상단의 초록색 뷰 컨트롤러는 오른쪽 상단의 뷰 컨트롤러를 제시합니다. 그리고 각 프리젠테이션 스타일의 결과는 아래에 보여집니다. 몇 가지 프리젠테이션의 경우 UIKit은 두 뷰 컨트롤러의 컨텐트 사이에 흐리게 보이는 뷰를 삽입합니다.

Figure 8-1 The full screen presentation styles

NOTE
UIModalPresentationFullScreen 스타일을 사용해 뷰 컨트롤러를 제시할 때에는 UIKit은 정상적으로 전환 애니메이션이 마친 이후에 기본 뷰 컨트롤러의 뷰를 제거합니다. 대신 UIModalPresentationOverFullScreen 스타일을 구체화함으로써 이 뷰들의 제거를 막을 수 있습니다. 제시되는 뷰가 기본 컨텐트를 보여줄 수 있도록 투명 영역을 갖고 있을 때 이 스타일을 사용하게 될 것입니다.

전체 스크린 프리젠테이션 스타일 중 한 가지를 사용할 때 뷰 컨트롤러는 프리젠테이션을 초기화하는 뷰 컨트롤러는 스스로 전체 스크린을 덮어야 합니다. 만약 제시하는 뷰 컨트롤러가 스크린을 덮지 않는다면, UIKit은 그렇게 할 수 있는 뷰 컨트롤러를 찾을 때까지 뷰 컨트롤러 계층구조를 탐색할 것입니다. 만약 스크린을 채울 수 있는 중간 뷰 컨트롤러를 찾을 수 없다면, UIKit은 윈도우의 루트 뷰 컨트롤러를 사용합니다.

The Popover Style

UIModalPresentationPopover 스타일은 팝오버 뷰에서 뷰 컨트롤러를 표시합니다. 팝오버는 추가적인 정보나 초점이 맞춰진 혹은 선택된 객체와 관련이 있는 아이템의 리스트를 보여주기에 유용합니다. 수평 regular 환경에서 팝오버 뷰는 Figure 8-2에 보이는 것처럼 스크린의 일부만을 덮습니다. 수평 compact 환경에서 팝오버는 기본값으로 UIModalPresentationOverFullScreen 프리젠테이션 스타일에 적응합니다. 팝오버 뷰 바깥을 탭하면 팝오버를 해제합니다.

Figure 8-2 The popover presentation style

팝오버는 수평 compact 환경에서 전체 스크린 프리젠테이션에 적응하기 때문에 적응을 처리하기 위해서는 팝오버 코드를 수정해야 할 필요가 있습니다. 전체 스크린 모드에서 제시된 팝오버를 해제하기 위한 방법이 필요할 것입니다. 버튼 추가, 해제 가능한 컨테이너 뷰 컨트롤러에 팝오버를 끼워넣는 것, 적응 동작 자체를 변경시키는 것을 통해 그렇게 할 수 있습니다.

팝오버 프리젠테이션을 설정하는 방법에 대한 더 많은 정보는 Presenting a View Controller in a Popover를 살펴보시기 바랍니다.

Presenting a View Controller in a Popover는 이 글의 아래 부분에 나옵니다.

The Current Context Styles

UIModalPresentationCurrentContext 스타일은 인터페이스에 있는 특정 뷰 컨트롤러를 덮습니다. 맥락이 있는 스타일을 사용할 때, definesPresentationContext 속성을 YES로 설정하는 것을 통해 덮길 원하는 뷰 컨트롤러를 지정해야 합니다. Figure 8-3은 스플릿 뷰 컨트롤러가 갖는 하나의 자식 뷰 컨트롤러만 덮는 현재 컨텍스트 프리젠테이션을 보여주고 있습니다.

Figure 8-3 The current context presentation style

NOTE
UIModalPresentationFullScreen 스타일을 사용해 뷰 컨트롤러를 제시할 때, UIKit은 정상적으로 전환 애니메이션이 끝난 후에 기본 뷰 컨트롤러의 뷰를 제거합니다. 이 뷰들을 제거하는 것을 방지하려면 UIModalPresentationOverCurrentContext 스타일을 대신 사용하면 됩니다. 기본 컨텐트를 보여줄 수 있도록 제시된 뷰 컨트롤러가 투명한 영역을 가지고자 할 때에도 이 스타일을 사용하면 됩니다.

프리젠테이션 맥락을 정의하는 뷰 컨트롤러는 프리젠테이션이 진행되는 동안 전환 애니메이션을 정의할 수 있습니다. UIKit은 제시되는 뷰 컨트롤러의 modalTransitionStyle 속성에 있는 값을 사용해 뷰 컨트롤러를 스크린으로 애니메이션을 통해 보여줍니다. 만약 프리젠테이션 컨텍스트 뷰 컨트롤러가 providesPresentationContextTransitionStyle 속성에 대해 YES로 설정되어 있는 경우 UIKit은 뷰 컨트롤러의 modalTransitionStyle 속성을 대신 사용하게 됩니다.

수평 compact 환경으로 전환할 때, 현재 컨텍스트 스타일은 UIModalPresentationFullScreen 스타일에 적응합니다. 이 동작을 변경하려면 다른 프리젠테이션이나 뷰 컨트롤러를 구체화할 수 있도록 적응형 프리젠테이션 딜리게이트를 사용해야 합니다.

Custom Presentation Styles

UIModalPresentationCustom 스타일은 직접 정의한 커스텀 스타일을 사용해 뷰 컨트롤러를 제시할 수 있도록 해줍니다. 커스텀 스타일을 생성하는 것은 UIPresentationController의 서브클래싱과 연관이 있으며, 스크린에 커스텀 뷰를 애니메이션 하는 것과 제시되는 뷰 컨트롤러의 크기와 위치를 설정하는 것에 대한 메소드를 사용하는 것과도 연관이 있습니다. 프리젠테이션 컨트롤러는 제시되는 뷰 컨트롤러의 특성에서 변경이 발생했을 때 모든 적응에 대한 진행을 처리하기도 합니다.

커스텀 프리젠테이션 컨트롤러에 대한 더 많은 정보는 Creating Custom Presentations를 살펴보시기 바랍니다.

Creating Custom Presentations는 이 글의 아래 부분에 나옵니다.

Transition Styles

전환 스타일은 제시되는 뷰 컨트롤러를 표시하기 위해 사용되는 애니메이션의 타입을 결정합니다. 내장된 전환 스타일의 경우 제시하고자 하는 뷰 컨트롤러의 modalTransiotionStyle에 표준 전환 스타일 중 한 가지를 할당하면 적용됩니다. 뷰 컨트롤러를 제시할 때 UIKit은 해당 스타일에 상응하는 애니메이션을 생성합니다. 예를 들어 Figure 8-4는 표준 슬라이드업 전환(UIModalTransitionStyleCoverVertical)이 어떻게 뷰 컨트롤러를 스크린으로 애니메이션하는지를 보여줍니다. 뷰 컨트롤러 B는 스크린 밖에서 시작해 위 방향으로 애니메이션이 되면서 뷰 컨트롤러 A의 상단까지 덮습니다. 뷰 컨트롤러 B가 해제되면, 애니메이션은 A를 다시 드러낼 수 있도록 B를 아래 방향으로 내립니다.

Figure 8-4 A transition animation for a view controller

애니메이터 객체나 전환 딜리게이트를 사용해 커스텀 전환을 생성할 수 있습니다. 뷰 컨트롤러를 스크린에 위치시키기 위해서 애니메이터 객체는 전환 애니메이션을 생성합니다. 전환 딜리게이트는 적절한 때에 UIKit에게 애니메이터 객체를 제공합니다. 커스텀 전환에 관한 더 많은 정보는 Customizing the Transition Animations를 살펴보시기 바랍니다.

Customizing the Transition Animations는 이 글의 아래 부분에 나옵니다.

Presenting Versus Showing a View Controller

UIViewController 클래스는 뷰 컨트롤러를 표시하기 위한 두 가지 방법을 제공합니다.

  • showViewController:sender:, showDetailViewController:sender: 메소드는 뷰 컨트롤러를 표시하는 가장 높은 수준의 적응성을 지니며 유연한 방법을 제공합니다. 이 메소드들은 뷰 컨트롤러를 제시하는 뷰 컨트롤러가 프리젠테이션을 최적의 방법으러 처리할 수 있도록 결정하게 해줍니다. 예를 들어 컨테이너 뷰 컨트롤러는 모달을 통해 전달하는 것 대신 자식으로써 뷰 컨트롤러를 통합시킬 것입니다. 기본값 동작은 뷰 컨트롤러를 모달로 제시합니다.

  • presentViewController:animated:completion: 메소드는 언제나 뷰 컨트롤러를 모달로 표시합니다. 이 메소드를 호출하는 뷰 컨트롤러는 궁극적으로 이 프리젠테이션을 처리하지는 않지만, 프리젠테이션은 항상 모달입니다. 이 메소드는 수평 compact 환경에서 프리젠테이션 스타일을 적용합니다.

howViewController:sender:, showDetailViewController:sender: 메소드는 프리젠테이션 초기화의 선호되는 방법입니다. 뷰 컨트롤러는 나머지 뷰 컨트롤러 계층구조 혹은 해당 계층구조에서 현재 뷰 컨트롤러의 위치에 대한 어떠한 정보도 없이 이 메소드들을 호출할 수 있습니다. 또한, 이 메소드들은 조건이 붙는 코드 경로를 작성하는 것 없이도 앱의 다른 부분들에서 뷰 컨트롤러의 재사용을 더 쉽게 만들어줍니다.

Presenting a View Controller

뷰 컨트롤러의 프리젠테이션을 초기화하는 방법은 여러 가지가 있습니다.

  • 뷰 컨트롤러를 자동으로 제시하기 위해 세그를 사용할 수 있습니다. 세그는 인터페이스 빌더에서 구체화한 정보를 사용해 뷰 컨트롤러를 인스턴스화 하고 제시합니다. 세그 설정에 관한 정보는 Using Segues를 살펴보시기 바랍니다.
  • showViewController:sender: 혹은 showDetailViewController:sender: 메소드를 사용해 뷰 컨트롤러를 표시할 수 있습니다. 커스텀 뷰 컨트롤러에서 뷰 의도하고 싶은 뷰 컨트롤러에 더 적합한 형태로 만들기 위해 이 메소드를 사용해 동작을 변경시킬 수 있습니다.
  • 뷰 컨트롤러를 모달로 제시하기 위해 presentViewController:animated:completion: 메소드를 호출하시기 바랍니다.

이 테크닉을 사용해 제시된 뷰 컨트롤러를 해제하기 위한 방법은 Dismissing a Presented View Controller에 나와있습니다.

Using Segues와 Dismissing a Presented View Controller는 이 글의 아래 부분에 나옵니다.

Showing View Controllers

showViewController:sender:, showDetailViewController:sender: 메소드를 사용할 때, 새로운 뷰 컨트롤러를 가져오는 과정은 간단합니다.

  1. 제시하고자 하는 뷰 컨트롤러를 생성해야 합니다. 뷰 컨트롤러를 생성할 때 작업을 수행하기 위해 필요한 데이터와 함께 뷰 컨트롤러를 초기화해야 할 필요가 있습니다.
  2. 새 뷰 컨트롤러의 modalPresentationStyle 속성을 원하는 프리젠테이션 스타일로 설정해야 합니다. 이 스타일은 마지막 프리젠테이션에서 사용되지 않을 수 있습니다.
  3. 뷰 컨트롤러의 modalTransitionStyle 속성을 원하는 전환 애니메이션 스타일로 설정해야 합니다.
  4. 현재 뷰 컨트롤러에서 showViewController:sender:, showDetailViewController:sender: 메소드를 호출해야 합니다.

UIKitshowViewController:sender:, showDetailViewController:sender: 메소드를 뷰 컨트롤러를 제시하는 적합한 뷰 컨트롤러에 전달합니다. 이를 통해 해당 뷰 컨트롤러는 어떤 방법이 프리젠테이션 수행에 최선인지를 결정할 수 있고, 필요한 경우 프리젠테이션과 전환 스타일을 변경시킬 수 있습니다. 예를 들어 네비게이션 컨트롤러는 네비게이션 스택에 뷰 컨트롤러를 추가할 것입니다.

뷰 컨트롤러를 보여주는 것과 모달로 제시하는 것 사이의 차이점에 관한 정보는 Presenting Versus Showing a View Controller에서 살펴보시기 바랍니다.

Presenting Versus Showing a View Controller는 이 글의 윗부분에 있습니다.

Presenting View Controllers Modally

뷰 컨트롤러를 직접 제시하려고 할 때, UIKit에게 새 뷰 컨트롤러를 어떻게 표시되길 원하는지에 대한 정보와 어떻게 스크린에서 애니메이션되어야 하는지에 대한 정보를 알려줘야 합니다.

  1. 제시하고자 하는 뷰 컨트롤러 객체를 생성해야 합니다.
    뷰 컨트롤러를 생성할 때 작업을 수행하기 위해 필요한 데이터와 함께 뷰 컨트롤러를 초기화해야 할 필요가 있습니다.
  2. 새 뷰 컨트롤러의 modalPresentationStyle 속성을 설정해 원하는 프리젠테이션 스타일을 적용할 수 있도록 해야 합니다.
  3. 뷰 컨트롤러의 modalTransitionStyle 속성을 설정해 원하는 애니메이션 스타일을 적용할 수 있도록 해야 합니다.
  4. 현재 뷰 컨트롤러의 presentViewController:animated:completion: 메소드를 호출해야 합니다.

presentViewController:animated:completion: 메소드를 호출하는 뷰 컨트롤러는 실제로 모달 프리젠테이션을 수행하는 객체는 아닐 것입니다. 프리젠테이션 스타일은 해당 뷰 컨트롤러가 제시되어야 하는지를 결정합니다. 이는 뷰 컨트롤러를 제시하는 뷰 컨트롤러에게 요구되는 특징을 포함합니다. 예를 들어 전체 스크린 프리젠테이션은 전체 스크린 뷰 컨트롤러에 의해 초기화되어야 합니다. 만약 뷰 컨트롤러를 제시하는 뷰 컨트롤러가 적합하지 않다면, UIKit은 적합한 한 가지를 찾기 위해 뷰 컨트롤러 계층구조를 탐색합니다. 모달 프리젠테이션이 완료되면, UIKit은 뷰 컨트롤러를 제시하는 뷰 컨트롤러와 제시되는 뷰 컨트롤러의 속성을 업데이트합니다.

Listing 8-1은 코드 작성을 통해 뷰 컨트롤러를 제시하는 방법을 설명하고 있습니다. 사용자가 새로운 레시피를 추가할 때, 앱은 네비게이션 컨트롤러를 제시함으로써 사용자에게 레시피에 대한 기본 정보를 묻습니다. 취소와 완료 버튼을 두기 위한 표준 위치가 있도록 네비게이션 컨트롤러가 선택되었습니다. 네비게이션 컨트롤러를 사용하는 것은 이후에 있을 새로운 레시피 인터페이스 확장을 더 쉽게 하기도 합니다. 해야 하는 것은 단지 네비게이션 스택에 새 뷰 컨트롤러를 추가하는 것 뿐입니다.

Listing 8-1
Presenting a view controller programmatically

- (void)add:(id)sender {
   // Create the root view controller for the navigation controller
   // The new view controller configures a Cancel and Done button for the
   // navigation bar.
   RecipeAddViewController *addController = [[RecipeAddViewController alloc] init];
 
   addController.modalPresentationStyle = UIModalPresentationFullScreen;
   addController.transitionStyle = UIModalTransitionStyleCoverVertical;
   [self presentViewController:addController animated:YES completion: nil];
}

Presenting a View Controller in a Popover

팝오버는 제시하기 전 추가적인 설정을 요구합니다. 모달 프리젠테이션 스타일을 UIModalPresentationPopover로 설정한 이후 팝오버와 관련된 다음 특성을 설정하시기 바랍니다.

  • 뷰 컨트롤러의 preferredContentSize 속성을 원하는 크기를 위해 설정해야 합니다.
  • 뷰 컨트롤러의 popoverPresentationController 속성으로부터 접근이 가능한 UIPopoverPresentationController 객체를 사용해 팝오버의 앵커 포인트를 설정해야 합니다. 아래 중 한 가지를 설정해야 합니다.
    • 바 버튼 아이템을 위해 barButtonItem 속성을 설정합니다.
    • 하나의 뷰에서 특정 영역에 sourceViewsourceRect 속성을 설정합니다.

팝오버의 모양에 필요한 다른 설정을 만들기 위해 UIPopoverPresentationController 객체를 사용할 수 있습니다. 팝오버 프리젠테이션 컨트롤러는 프리젠테이션 처리가 일어나는 동안 변경에 반응할 수 있도록 사용이 가능한 딜리게이트 객체를 지원하기도 합니다. 예를 들어 스크린에 팝오버가 나타나거나 사라지거나 위치가 조정될 때 반응하는 딜리게이트를 사용할 수 있습니다. 이 객에 관한 더 많은 정보는 UIPopoverPresentationController 클래스 레퍼런스를 살펴보시기 바랍니다.

UIPopoverPresentationController
https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller

Dismissing a Presented View Controller

제시되고 있는 뷰 컨트롤러를 해제하려면, 제시하고 있는 뷰 컨트롤러의 dismissViewControllerAnimated:completion: 메소드를 호출해야 합니다. 이 메소드를 제시되고 있는 뷰 컨트롤러 스스로가 호출하도록 할 수도 있습니다. 제시되고 있는 뷰 컨트롤러에 이 메소드를 호출할 때, UIKit은 제시하고 있었던 뷰 컨트롤러에게 자동으로 요청을 전달합니다.

뷰 컨트롤러를 해제하기 전에 중요한 정보를 항상 저장해야 합니다. 뷰 컨트롤러를 해제하는 것은 뷰 컨트롤러 계층구조로부터 정보를 지웁니다. 그리고 스크린으로부터 뷰 역시 제거합니다. 만약 어딘가에 저장된 뷰 컨트롤러를 강한 참조로 갖고 있지 않다면, 이 뷰 컨트롤러를 해제하는 것은 이 뷰 컨트롤러에 관련된 메모리를 해제합니다.

제시되고 있는 뷰 컨트롤러가 제시하고 있는 뷰 컨트롤러에게 데이터를 반환해야 한다면, 전송이 가농하도록 딜리게이션 디자인 패턴을 사용하시기 바랍니다. 딜리게이션은 앱의 다른 부분에 있어서도 뷰 컨트롤러의 재사용을 쉽게 만들어줍니다. 딜리게이션을 활용하면 제시되는 뷰 컨트롤러는 프로토콜을 통해 메소드를 구현하는 딜리게이트 객체에 참조를 저장합니다. 이에 대한 결과를 모을 때, 제시되는 뷰 컨트롤러는 딜리게이트에서 해당 메소드들을 호출합니다. 보통 구현에 있어 제시하고 있는 뷰 컨트롤러는 스스로 제시되는 뷰 컨트롤러의 딜리게이트를 스스로 만듭니다.

Presenting a View Controller Defined in a Different Storyboard

같은 스토리 보드 내에서 뷰 컨트롤러 사이에 세그를 생성할 수 있을지라도 스토리보드 사이의 세그는 생성할 수 없습니다. 다른 스토리보드에 저장된 뷰 컨트롤러를 보여주길 원한다면, Listing 8-2에 보이는 것처럼 해당 뷰 컨트롤러를 제시하기 전에 명시적으로 인스턴스화 해야 합니다. 예시는 뷰 컨트롤러를 모달로 제시합니다. 네비게이션 컨트롤러에 넣을 수도 있고, 다른 방법으로 표시할 수도 있습니다.

Listing 8-2 Loading a view controller from a storyboard

UIStoryboard* sb = [UIStoryboard storyboardWithName:@"SecondStoryboard" bundle:nil];
MyViewController* myVC = [sb instantiateViewControllerWithIdentifier:@"MyViewController"];
 
// Configure the view controller.
 
// Display the view controller
[self presentViewController:myVC animated:YES completion:nil];

앱에서 여러 스토리보드를 만들 필요는 없습니다. 그럼에도 여러 스토리보드가 유용한 몇 가지 경우를 아래에서 보여주고 있습니다.

  • 팀이 파트로 나눠져 있으면서 할당된 UI의 부분이 다를 정도로 거대한 프로그래밍 팀을 갖고 있는 경우에 그렇습니다. 각 팀은 충돌을 피하기 위해 팀마다 맡고 있는 UI를 다른 스토리보드 파일에서 관리할 수 있도록 할 수 있습니다.
  • 뷰 컨트롤러 타입의 컬렉션을 사전에 정의하고 있는 라이브러리를 구입했거나 생성했을 경우가 그렇습니다. 해당 뷰 컨트롤러들의 컨텐츠는 라이브러리에서 제공하는 스토리보드에 정의되어 있을 것입니다.
  • 외부 스크린에서 보여져야 할 필요가 있는 컨텐트를 갖고 있을 수도 있습니다. 이 경우 분리된 스토리보드 내에서 대체 스크린에 연관된 모든 뷰 컨트롤러를 유지하고자 할 것입니다. 같은 시나리오에서 대안 패턴은 커스텀 세그를 작성하는 것입니다.

Using Segues

앱의 인터페이스 흐름을 정의하기 위해 세그를 사용하시기 바랍니다. 세그는 앱의 스토리보드 파일에 있는 두 뷰 컨트롤러 사이의 전환을 정의합니다. 세그의 시작점은 세그를 초기화할 수 있는 버튼, 테이블 행, 제스쳐 리코그나이저입니다. 세그의 끝점은 보여주길 원하는 뷰 컨트롤러입니다. 세그는 항상 새로운 뷰 컨트롤러를 제시합니다. 또한, 뷰 컨트롤러를 해제하기 위해 언와인드(unwind) 세그를 사용할 수도 있습니다.

Figure 9-1 A segue between two view controllers

세그를 코드 작성으로 만들 필요는 없습니다. 런타임에 UIKit은 관련이 있는 뷰 컨트롤러의 세그를 로드하고, 세그에 상응하는 요소들을 연결시켜줍니다. 사용자가 해당 요소에 상호작용하면, UIKit은 적합한 뷰 컨트롤러를 로드하고, 세그가 발생할 예정이라는 것을 앱에 알려줍니다. 그리고 전환을 수행합니다. 새 뷰 컨트롤러에 데이터를 전달하기 위해 UIKit으로부터 받은 노티피케이션을 사용할 수도 있고, 세그가 전혀 발생하지 않도록 노티피케이션을 사용할 수도 있습니다.

Creating a Segue Between View Controllers

같은 스토리보드 파일 내에서 뷰 컨트롤러 사이의 세그를 생성하려면, 키보드의 컨트롤 키를 누른 채로 첫 번째 뷰 컨트롤러에 있는 적합한 요소를 클릭한 후 목표 뷰 컨트롤러에 드래그하면 됩니다.세그의 시작점은 컨트롤, 바 버튼 아이템, 제스쳐 리코그나이저처럼 정의된 액션을 갖는 뷰 혹은 객체여야 합니다. 또한, 테이블뷰와 컬렉션뷰처럼 셀에 기반한 뷰로부터 세그를 생성할 수도 있습니다. Figure 9-2는 테이블 행이 탭되었을 때 새로운 뷰 컨트롤러를 표시하는 세그의 생성을 보여줍니다.

Figure 9-2 Creating the segue relationship

NOTE
몇 가지 요소들은 다중 세그를 지원합니다. 예를 들어 테이블 행은 행의 액세서리 버튼과 남은 부분의 탭에 서로 다른 세그를 설정할 수 있습니다.

마우스 버튼을 놓으면 Figure 9-3에 보이는 것처럼 인터페이스 빌더는 두 뷰 컨트롤러 사이에서 생성하고자 하는 관계의 타입을 선택하도록 작은 창을 띄웁니다. 원하는 전환을 위해 해당하는 세그를 선택하면 됩니다.

Figure 9-3 Selecting the type of segue to create

세그에서 관계 타입을 선택할 때 가능한 적응형 세그르 선택하기 바랍니다. 적응형 세그는 현재 환경에 기반해 자동으로 작동을 조정합니다. 예를 들어 쇼 세그의 작동은 뷰 컨트롤러를 제시하는 뷰 컨트롤러에 기반해 변경됩니다. 비적응형 세그는 적응형 세그를 지원하지 않는 iOS 7에서도 작동해야 하는 앱에서 제공됩니다. Table 9-1은 적응형 세그에 대한 목록을 보여주고 있으며, 앱에서 어떻게 작동하는지에 대한 내용도 있습니다.

Table 9-1 Adaptive segue types

Segue TypeBehavior
Show (Push)이 세그는 목표 뷰 컨트롤러의 showViewController:sender: 메소드를 사용해 새 컨텐트를 보여줍니다. 대부분의 뷰 컨트롤러에서 이 세그는 소스 뷰 컨트롤러를 통해 모달로 새 컨텐트를 제시합니다. 몇 가지 뷰 컨트롤러는 이 메소드를 오버라이드하고 다른 작동을 구현하기 위해 사용하기도 합니다. 예를 들어 네비게이션 컨트롤러는 새 뷰 컨트롤러를 네비게이션 스택에 넣습니다. UIKit은 소스 뷰 컨트롤러를 위치시키기 위해 targetViewControllerForAction:sender: 메소드를 사용합니다.
Show Detail (Replace)이 세그는 목표 뷰 컨트롤러의 showDetailViewController:sender: 메소드를 사용해 새 컨텐트를 표시합니다. 이 세그는 UISplitViewController 객체 내부에 끼워진 뷰 컨트롤러에만 관련이 있습니다. 이 세그를 사용하면 스플릿 뷰 컨트롤러는 두 번째 자식 뷰 컨트롤러(디테일 컨트롤러)를 새로운 컨텐트로 교체합니다. 대부분 다른 뷰 컨트롤러는 세 컨텐트를 모달로 제시합니다. UIKittargetViewControllerForAction:sender: 메소드를 사용해 소스 뷰 컨트롤러를 위치시킵니다.
Present Modally이 세그는 구체화된 프리젠테이션과 전환 사타일을 사용해 모달로 뷰 컨트롤러를 표시합니다. 적합한 프리젠테이션 컨텍스트를 정의한 뷰 컨트롤러는 실제 프리젠테이션을 처리합니다.
Present as Popover수평 regular 환경에서 뷰 컨트롤러는 팝오버에 나타납니다. 수평 compact 환경에서 뷰 컨트롤러는 전체 스크린 모달 프리젠테이션을 사용해 표시됩니다.

세그를 생성한 후 세그 객체를 선택하고 특성 인스펙터를 사용해 아이덴티파이어를 할당해야 합니다. 세그가 진행되는 동안 어떤 세그가 발생한 것인지 확인할 수 있도록 아이덴티파이어를 사용할 수 있습니다. 이는 뷰 컨트롤러가 다중 세그를 지원하는 경우 특히 유용합니다. 아이덴티파이어는 세그가 수행될 때 뷰 컨트롤러에 전달된 UIStoryboardSegue 객체에 포함되어 있습니다.

Modifying a Segue’s Behavior at Runtime

Figure 9-4는 세그가 이뤄질 때 어떤 일이 발생하는지를 보여줍니다. 대부분의 작동은 새 뷰 컨트롤러에 대한 전환을 다루는 뷰 컨트롤러에서 발생합니다. 새 뷰 컨트롤러의 설정은 근본적으로 뷰 컨트롤러를 생성할 때와 제시할 때의 과정과 같습니다. 세그는 스토리보드에서 설정되기 때문에 세그에 연관된 뷰 컨트롤러들은 같은 스토리보드 안에 존재해야 합니다.

Figure 9-4 Displaying a view controller using a segue

세그가 발생하는 동안 UIKit은 세그의 결과를 다루기 위한 기회를 제공하고자 현재 뷰 컨트롤러의 메소드를 호출합니다.

  • shouldPerformSegueWithIdentifier:sender: 메소드는 세그가 발생하는 것을 막을 수 있는 기회를 제공합니다. 이 메소드로부터 NO를 반환하는 것은 세그가 발생하는 것을 방지하되 다른 액션 발생은 막지 않습니다. 예를 들어 테이블 행의 탭은 관련이 있는 딜리게이트 메소드를 여전히 호출할 수 있도록 합니다.
  • 소스 뷰 컨트롤러의 prepareForSegue:sender: 메소드는 소스 뷰 컨트롤러로부터 목적 뷰 컨트롤러에 데이터가 전달될 수 있도록 해줍니다. 해당 메소드에서 전달된 UIStoryboardSegue 객체는 세그 관련 정보와 함께 목적 뷰 컨트롤러에 대한 참조를 포함합니다.

Creating an Unwind Segue

언와인드 세그는 제시되었던 뷰 컨트롤러를 해제할 수 있도록 해줍니다. 현재 뷰 컨트롤러의 Exit 객체에 버튼이나 기타 적합한 객체를 연결하는 것을 통해 인터페이스 빌더에서 언와인드 세그를 생성할 수 있습니다. 사용자가 버튼을 탭하거나 적합한 객체에 상호작용하면, UIKit은 언와인드 세그를 처리할 객체를 뷰 컨트롤러 계층구조에서 탐색합니다. 그러면 언와인드 세그의 목표를 표시하기 위해 현재 뷰 컨트롤러와 모든 중개자 뷰 컨트롤러를 해제합니다.

언와인드 세그를 생성하려면
1. 언와인드 세그가 마무리될 때 화면에 나타나야 할 뷰 컨트롤러를 선택해야 합니다.
2. 선택한 뷰 컨트롤러에 언와인드 액션을 정의해야 합니다.

스위프트 표기법은 아래와 같습니다.

@IBAction func myUnwindAction(unwindSegue: UIStoryboardSegue)

Objective-C 표기법은 아래와 같습니다.

- (IBAction)myUnwindAction:(UIStoryboardSegue*)unwindSegue
  1. 언와인드 액션을 초기화할 뷰 컨트롤러를 탐색해야 합니다.

  2. 컨트롤 키를 누른 채로 언와인드 세그를 초기화해야 하는 버튼(혹은 다른 객체)를 클릭합니다. 이 요소는 해제하고자 하는 뷰 컨트롤러에 존재해야 합니다.

  3. 뷰 컨트롤러 씬의 상단에 있는 Exit 객체에 드래그합니다.

  1. 관련이 있는 패널로부터 언와인드 액션 메소드를 선택합니다.

인터페이스 빌더에서 상응하는 언와인드 액션을 생성하길 시도하기 전에 뷰 컨트롤러 중 하나에서 언와인드 액션 메소드를 정의해야 합니다. 해당 메소드의 존재는 필수적이고, 언와인드 세그에 대해 유효한 목표가 있다고 인터페이스 빌더에게 알려줍니다.

앱과 관련된 모든 작업을 수행하기 위해 언와인드 액션 메소드의 구현을 사용해야 합니다. 세그에 연관된 모든 뷰 컨트롤러를 해제할 필요는 없습니다. UIKit이 그렇게 합니다. 대신 해제되는 뷰 컨트롤러를 가져오기 위해 세그 객체를 사용해야 합니다. 그렇게 함으로써 데이터를 회수할 수 있습니다. 또한, 언와인드 세그가 마무리되기 전에 현재 뷰 컨트롤러를 업데이트하기 위해 언와인드 액션을 사용할 수 있습니다.

Initiating a Segue Programmatically

세그는 스토리보드 파일에서 생성한 연결 때문에 발생됩니다. 그러나 스토리보드에서 세그를 생성할 수 없을 때가 있습니다. 목적 뷰 컨트롤러가 알려지지 않았기 때문일 것입니다. 예를 들어 게임앱은 게임의 결과에 따라 다른 스크린에 전환될 것입니다. 이와 같은 상황에서 현재 뷰 컨트롤러의 performSegueWidhIdentifier:sender: 메소드를 코드로 작성해 세그를 생성할 수 있습니다.

Listing 9-1은 portrait에서 landscape으로 전환될 때 특정 뷰 컨트롤러를 제시하는 세그를 보여주고 있습니다. 이 경우에서 노티피케이션 개체는 세그 커맨드를 수행하기에 유용한 정보를 제공하지 않기 때문에 뷰 컨트롤러는 세그의 센더로 스스로를 지정합니다.

Listing 9-1 Triggering a segue programmatically

- (void)orientationChanged:(NSNotification *)notification {
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
             !isShowingLandscapeView) {
        [self performSegueWithIdentifier:@"DisplayAlternateView" sender:self];
        isShowingLandscapeView = YES;
    }
// Remainder of example omitted.
}

Creating a Custom Segue

인터페이스 빌더는 하나의 뷰 컨트롤러에서 다른 뷰 컨트롤러로 전환하는 것과 뷰 컨트롤러를 제시하는 뷰 컨트롤러로부터 팝오버에서 컨트롤러를 표시하는 것으로의 전환에 관련된 모든 표준 방법을 제공합니다. 그러나 만약 원하는 것이 없다면 커스텀 세그를 생성할 수 있습니다.

The Life Cycle of a Segue

커스텀 세그가 어떻게 작동하는지를 이해하려면 세그 객체의 생명주기를 이해해야 합니다. 세그 객체는 UIStoryboardSegue 클래스 혹은 이 클래스의 서브클래스의 인스턴스입니다. 앱은 절대로 세그 객체를 직접 생성하지 않습니다. UIKit은 세그가 시도될 때 세그를 생성합니다. 어떤 일이 일어나는지는 아래와 같습니다.

  1. 제시되고자 하는 뷰 컨트롤러는 생성되고 초기화됩니다.
  2. 세그 객체는 생성되고 세그 객체의 initWithIdentifier:source:destination 메소드가 호출됩니다. 아이덴티파이어는 인터페이스 빌더에서 세그에 대해 제공된 고유한 스트링입니다. 그리고 다른 두 파라미터는 전환에서 두 컨트롤러 객체를 나타냅니다.
  3. 뷰 컨트롤러를 제시하는 뷰 컨트롤러의 prepareForSegue:sender 메소드가 호출됩니다. 앞서 설명한 Modifying a Segue's Behavior at Runtime을 살펴보시기 바랍니다.
  4. 세그 객체의 수행 메소드가 호출됩니다. 이 메소드는 새 뷰 컨트롤러를 스크린에 가져오기 위해 전환을 수행합니다.
  5. 세그 객체의 참조가 해제됩니다.

Implementing a Custom Segue

커스텀 세그를 구현하려면 UIStoryboardSegue를 서브클래싱하고 아래 메소드를 구현해야 합니다.

  • the initWithIdentifier:source:destination: 메소드를 오버라이드 하고, 커스텀 세그 객체를 초기화하기 위해 오버라이드 한 메소드를 사용해야 합니다. 항상 super를 먼저 호출합니다.
  • 수행 메소드를 구현하고 전환 애니메이션을 설정하기 위해 이 수행 메소드를 사용해야 합니다.

NOTE
만약 구현이 세그를 설정하기 위해 속성을 추가하고 있다면, 인터페이스 빌더에서 이러한 특성들을 설정할 수 없습니다. 대신 세그를 시도하는 소스 뷰 컨트롤러의 prepareForSegue:sender: 메소드에서 커스텀 세그의 추가적인 속성들을 설정해야 합니다.

Listing 9-2는 매우 간단한 커스텀 세그를 보여주고 있습니다. 이 예시는 어떤 종류의 애니메이션 없이 목적 뷰 컨트롤러를 간단하게 제시합니다. 필요하다면 이 아이디어를 직접 고안한 애니메이션으로 확장시킬 수 있습니다.

Listing 9-2 A custom segue

- (void)perform {
    // Add your own animation code here.
 
    [[self sourceViewController] presentViewController:[self destinationViewController] animated:NO completion:nil];
}

Customizing the Transition Animations

전환 애니메이션은 앱의 인터페이스 변경에 대한 시각적 피드백을 제공합니다. UIKit은 뷰 컨트롤러를 제시하고자 할 때 사용하기 위한 표준 전환 스타일의 세트를 제공합니다. 그리고 커스텀 전환을 통해 표준 전환을 보충할 수도 있습니다.

The Transition Animation Sequence

전환 애니메이션은 하나의 뷰 컨트롤러에 있는 컨텐츠를 다른 뷰 컨트롤러의 컨텐츠로 교체합니다. 전환에는 프리젠테이션과 해제(dismissal)라는 두 가지 유형이 존재합니다. 프리젠테이션 전환은 앱의 뷰 컨트롤러 계층구조에 새 뷰 컨트롤러를 추가하는 반면, 해제 전환은 계층구조로부터 하나 혹은 하나 이상의 뷰 컨트롤러를 제거합니다.

전환 애니메이션을 구현하기 위해서는 많은 객체가 필요합니다. UIKit은 전환과 관련해 모든 객체의 기본값 버전을 제공합니다. 그리고 이들 모두를 커스터마이징하거나 일부만 커스터마이징할 수도 있습니다. 올바른 객체 집합을 선택하면, 단지 적은 양의 코드로 애니메이션을 생성할 수 있어야 합니다. 심지어 상호작용을 포함하는 애니메이션은 UIKit이 제공하는 기존 코드를 활용한다면 쉽게 구현될 수 있습니다.

The Transitioning Delegate

전환 딜리게이트는 전환 애니메이션과 커스텀 프리젠테이션의 시작점입니다. 전환 딜리게이트는 직접 정의할 수 있는 객체이자 UIViewControllerTransitioningDelegate 프로토콜을 따르는 객체입니다. 그 작업은 UIKit에게 아래에 나열된 객체를 제공하는 것을 통해 이뤄집니다.

  • Animator objects. 애니메이터 객체는 뷰 컨트롤러의 뷰를 드러내거나 숨기기 위해 사용되는 애니메이션을 생성하는 데 책임이 있습니다. 전환 딜리게이트는 뷰 컨트롤러를 제시하는 것과 해제하는 것에 대한 개별 애니메이터 객체를 제공할 수 있습니다. 애니메티여 객체는 UIViewControllerAnimatedTransitioning 프로토콜을 따르고 있습니다.

  • Interactive animator objects. 상호작용 애니메이터 객체는 터치 이벤트나 제스쳐 리코그나이저를 사용해 커스텀 애니메이션의 타이밍을 결정합니다. 상호작용. 애니메이터 객체는 UIViewControllerInteractiveTransitioning 프로토콜을 따르고 있습니다.
    상호작용 애니메이터를 생성하는 가장 쉬운 방법은 UIPercentDrivenInteractiveTransition 클래스를 서브클래싱하고, 서브클래스에 이벤트 처리 코드를 추가하는 것입니다. 해당 클래스는 기존 애니메이터 객체를 사용해 생성된 애니메이션의 타이밍을 제어합니다. 만약 고유한 상호작용 애니메이터를 생성한다면, 애니메이션의 각 프레임을 직접 렌더링해야 합니다.

  • Presentation controller. 프리젠테이션 컨트롤러는 뷰 컨트롤러가 스크린에 보여지는 동안 프리젠테이션 스타일을 관리합니다. 시스템은 내장된 프리젠테이션 스타일을 위한 프리젠테이션 컨트롤러를 제공합니다. 그리고 고유한 프리젠테이션 스타일을 위해 커스텀 프리젠테이션 컨트롤를 직접 만들 수도 있습니다. 커스텀 프리젠테이션 컨트롤러 생성에 대한 더 많은 정보는 Creating Custom Presentations를 참고하시기 바랍니다.

Creating Custom Presentations는 이 글의 아래 부분에 나옵니다.

뷰 컨트롤러의 transitioningDelegate 속성에 전환 딜리게이트를 할당하는 것은 UIKit에게 커스텀 전환 혹은 프리젠테이션을 수행하고자 한다는 것을 알려줍니다. 딜리게이트는 어떤 객체가 제공되어야 하는지에 대해 선택적입니다. 애니메이터 객체를 제공하지 않는다면, UIKit은 뷰 컨트롤러의 modalTransitionStyle 속성에서 표준 전환 애니메이션을 사용합니다.

Figure 10-1은 전환 딜리게이트 및 애니메이터 객체와 제시되는 뷰 컨트롤러의 관계를 보여줍니다. 프리젠테이션 컨트롤러는 뷰 컨트롤러의 modalPresentationStyle 속성이 UIModalPresentationCustom으로 설정되었을 때에만 사용됩니다.

Figure 10-1 The custom presentation and animator objects

전환 딜리게이트 사용에 대한 더 많은 정보는 Implementing the Transitioning Delegate를 살펴보시기 바랍니다. 전환 딜리게이트 객체의 메소드에 대한 더 많은 정보는 UIViewControllerTransitioningDelegate 프로토콜 레퍼런스를 참고하시기 바랍니다.

Implementing the Transitioning Delegate는 이 글의 아래 부분에 나옵니다.

UIViewControllerTransitioningDelegate
https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate

The Custom Animation Sequence

제시되는 뷰 컨트롤러의 transitioningDelegate 속성이 유효한 객체를 포함하고 있을 때, UIKit은 직접 생성한 커스텀 애니메이터를 사용해 해당 뷰 컨트롤러를 제시합니다. 프리젠테이션이 준비되면 UIKit은 커스텀 애니메이터 객체를 회수하기 위해 전환 딜리게이트의 animationControllerForPresentedController:presentingController:sourceController: 메소드를 호출합니다. 만약 객체가 사용 가능하다면, UIKit은 아래 단계를 수행합니다.

  1. UIKit은 상호작용 애니메이터 객체가 사용이 가능한 경우를 살펴보기 위해 전환 딜리게이트의 interactionControllerForPresentation: 메소드를 호출합니다. 이 메소드가 nil을 반환하면, UIKit은 사용자 상호작용 없이 애니메이션을 수행합니다.
  2. UIKit은 애니메이션 듀레이션을 가져오기 위해 애니메이터 객체의 transitionDuration 메소드를 ㅎ호출합니다.
  3. UIKit은 애니메이션을 시작하기 위한 적합한 메소드를 호출합니다.
    • 상호작용 애니메이션이 없는 경우 UIKit은 애니메이터 객체의 animateTransition: 메소드를 호출합니다.
    • 상호작용 애니메이션의 경우 UIKit은 상호작용 애니메이터 객체의 startInteractiveTransition: 메소드를 호출합니다.
  4. UIKit은 컨텍스트 전환 객체의 completeTransition: 메소드를 호출하기 위해 애니메이터 객체를 기다립니다.
    애니메이션이 마무리된 후 커스텀 애니메이터가 이 메소드를 호출합니다. 보통은 애니메이션의 컴플리션 블록에서 이뤄집니다. 이 메소드를 허출하는 것은 전환을 마무리하고, UIKit에게 UIKitpresentViewController:animated:completion: 메소드의 컴플리션 핸들러를 처리할 수 있다는 것을 알려줍니다. 그리고 UIKit에게 애니메이터 객체가 갖는 animationEnded: 메소드를 호출할 수 있다는 것을 알려줍니다.

뷰 컨트롤러를 해제할 때 UIKit은 전환 딜리게이트의 animationControllerForDismissedController: 메소드를 호출하고, 아래 단계를 수행합니다.

  1. UIKit은 상호작용 애니메이터 객체가 사용 가능한지를 확인하기 위해 전환 딜리게이트의 interactionControllerForDismissal: 메소드를 호출합니다. 만약 해당 메소드가 nil을 반환하면, UIKit은 사용자 상호작용 없이 애니메이션을 수행합니다.
  2. UIKit은 애니메이션 듀레이션을 가져오기 위해 애니메이터 객체의 transitionDuration: 메소드를 호출합니다.
  3. UIKit은 애니메이션을 시작하기 위해 적합한 메소드를 호출합니다.
    • 상호작용이 없는 애니메이션의 경우 UIKit은 애니메이터 객체의 animateTransition: 메소드를 호출합니다.
    • 상호작용 애니메이션의 경우 UIKit은 상호작용 애니메이터 객체의 startInteractiveTransition: 메소드를 호출합니다.
  4. UIKit은 컨텍스트 전환 객체의 메소드인 completeTransition: 메소드를 호출하기 위해 애니메이터 객체를 기다립니다.
    커스텀 애니메이터는 애니메이션이 마무리된 후 이 메소드를 호출합니다. 보통 애니메이션의 컴플리션 블록에 있습니다. 이 메소드를 호출하는 것은 전환을 마무리하고, UIKitpresentViewController:animated:completion: 메소드의 컴플리션 핸들러를 호출할 수 있다는 것을 알려줍니다. 그리고 애니메이터 객체가 갖는 animationEnded: 메소드를 호출할 수 있다는 것을 UIKit에게 알려줍니다.

IMPORTANT
애니메이션이 마무리되는 시점에 completeTransition: 메소드를 호출하는 것은 필수적입니다. UIKit은 전환 프로세스를 마무리하지 않습니다. 이 메소드를 호출할 때까지 앱에서 컨트롤을 반환합니다.

The Transitioning Context Object

전환 애니메이션 시작 전에 UIKit은 전환 컨텍스트 객체를 생성하고, 이 객체를 어떻게 애니메이션을 수행할지에 대한 정보를 채웁니다. 전환 컨텍스트 객체는 코드에서 중요한 부분입니다. 이는 UIViewControllerContextTransitioning 프로토콜을 구현하고, 전환에 관여하는 뷰 컨트롤러와 뷰에 대한 참조를 저장합니다. 또한, 애니메이션이 상호작용인지에 대한 여부를 포함해 전환을 어떻게 수행해야 하는지에 대한 정보를 저장합니다. 애니메이터 객체는 실제 애니메이션을 설정하고 수행하기 위한 모든 정보가 필요합니다.

IMPORTANT
커스텀 애니메이션을 설정할 때, 직접 관리하는 캐시된 정보보다는 전환 컨텍스트 객체에 있는 객체와 데이터를 사용해야 합니다. 전환은 여러 조건에서 발생할 수 있습니다. 몇 가지 경우는 애니메이션 파라미터를 변경할 것입니다. 전환 컨텍스트 객체는 애니메이션을 수행하기에 필요한 정확한 정보를 갖는 것이 보장하는 반면 캐시된 정보는 애니메이터의 메소드가 호출될 때까지 유효하지 않을 수 있습니다.

Figure 10-2는 전환 컨텍스트 객체가 다른 객체와 어떻게 상호작용하는지를 보여줍니다. 애니메이터 객체는 애니메이터 객체의 animateTransition: 메소드에서 객체를 받습니다. 생성한 애니메이션은 제공받은 컨테이너 뷰 내부에 위치해야 합니다. 예를 들어 뷰 컨트롤러를 제시할 때 컨테이너 뷰의 하위뷰로써 뷰 컨트롤러의 뷰를 추가해야 합니다. 컨테이너 뷰는 윈도우 혹은 regular 뷰가 될 것입니다. 그리고 애니메이션을 작동시키기 위해 항상 설정된 형태가 됩니다.

Figure 10-2 The transitioning context object

전환 컨텍스트 객체에 대한 더 많은 정보는 UIViewControllerContextTransitioning 프로토콜 레퍼런스를 참고하시기 바랍니다.

UIViewControllerContextTransitioning
https://developer.apple.com/documentation/uikit/uiviewcontrollercontexttransitioning

The Transition Coordinator

내장된 전환과 커스텀 전환 두 가지 모두 UIKit은 수행에 필요한 모든 추가 애니메이션을 용이하게 할 수 있도록 전환 조정자 객체를 생성합니다. 뷰 컨트롤러의 프리젠테이션과 해제 외에도 전환은 인터페이스 회전이 발생하거나 뷰 컨트롤러의 프레임이 변경될 때에도 발생할 수 있습니다. 이 전환 모두는 뷰 계층구조에 변경사항을 나타냅니다. 전환 조정자는 이와 같은 변경사항을 추적하기 위한 방법이며, 동시에 컨텐트를 애니메이트할 수 있는 방법이기도 합니다. 전환 조정자에 접근하려면 영향을 받는 뷰 컨트롤러의 transitionCoordinator 속성에서 객체를 가져와야 합니다. 전환 조정자는 전환의 듀레이션에만 존재합니다.

Figure 10-3은 프리젠테이션에 관여하는 뷰 컨트롤러와 전환 조정자의 관계를 보여줍니다. 전환에 대한 정보를 가져오기 위해 전환 조정자를 사용할 수 있고, 전환 애니메이션 발생 시 동시에 수행되길 원하는 애니메이션 블록을 등록하기 위해서도 전환 조정자를 사용할 수 있습니다. 전환 조정자 객체는 타이밍 정보, 애니메이션의 현재 상태 정보, 전환에 관여하는 뷰와 뷰 컨트롤러를 제공하는 UIViewControllerTransitionCoordinatorContext 프로토콜을 따릅니다. 애니메이션 블록이 실행될 때, 이들은 유사하게 같은 정보를 갖는 컨텍스트 객체를 받습니다.

Figure 10-3 The transition coordinator objects

전환 조정자 객체에 대한 더 많은 정보는 UIViewControllerTransitionCoordinator 프로토콜 레퍼런스를 참고하시기 바랍니다. 애니메이션을 설정하기 위해 사용할 수 있는 맥락의 정보에 대한 더 많은 내용은 UIViewControllerTransitionCoordinatorContext 프로토콜 레퍼런스에서 볼 수 있습니다.

UIViewControllerTransitionCoordinator
https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioncoordinator

UIViewControllerTransitionCoordinatorContext
https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioncoordinatorcontext

Presenting a View Controller Using Custom Animations

커스텀 애니메이션을 사용해 뷰 컨트롤러를 제시하려면 기존 뷰 컨트롤러의 액션 메소드에서 아래 내용을 진행하시기 바랍니다.

  1. 제시하고자 하는 뷰 컨트롤러를 생성합니다.
  2. 커스텀 전환 딜리게이트 객체를 생성하고, 이 객체를 뷰 컨트롤러의 transitioningDelegate 속성에 할당해야 합니다.
  3. 뷰 컨트롤러를 제시하기 위해 presentViewController:animated:completion: 메소드를 호출해야 합니다.

presentViewController:animated:completion: 메소드를 호출할 때, UIKit은 프리젠테이션 프로세스를 초기화합니다. 프리젠테이션은 다음 런 루프 반복 동안 시작되고, 커스텀 애니메이터가 completTransition: 메소드를 호출할 때까지 계속합니다. 상호작용 전환은 전환이 진행되는 동안 터피 이벤트를 처리할 수 있도록 해줍니다. 상호작용이 없는 전환은 애니메이터 객체에서 구체화된 듀레이션 동안에만 작동합니다.

Implementing the Transitioning Delegate

전환 딜리게이트의 목적은 커스텀 객체를 생성하고 반환하는 것입니다. Listing 10-1은 전환 메소드의 구현이 얼마나 간단해질 수 있는지를 보여줍니다. 이 예시는 커스텀 애니메이터 객체를 생성하고 반환합니다. 대부분의 실제 작동은 애니메이터 객체 스스로에 의해 처리됩니다.

Listing 10-1 Creating an animator object

- (id<UIViewControllerAnimatedTransitioning>)
    animationControllerForPresentedController:(UIViewController *)presented
                         presentingController:(UIViewController *)presenting
                             sourceController:(UIViewController *)source {
    MyAnimator* animator = [[MyAnimator alloc] init];
    return animator;
}

전환 딜리게이트의 다른 메소드는 앞서 보여준 코드 만큼 간단합니다. 앱의 현재 상태에 기반해 다른 애니메이터 객체를 반환하고자 커스텀 로직을 통합할 수도 있습니다. 전환 딜리게이트의 메소드에 대한 정보는 UIViewControllerTransitioningDelegate 프로토콜 레퍼런스를 참고하시기 바랍니다.

UIViewControllerTransiotioningDelegate0
https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate

Implementing Your Animator Objects

애니메이션 객체는 UIViewControllerAnimatedTransitioning 프로토콜을 채택하는 모든 객체입니다. 애니메이터 객체는 고정된 시간에 걸쳐 수행하는 애니메이션을 생성합니다. 애니메이터 객체의 핵심은 실제 애니메이션을 생성하기 위해 사용하는 animateTransition: 메소드입니다. 애니메이션 프로세스는 대략적으로 아래처럼 나눠집니다.

  1. 애니메이션 파라미터를 가져옵니다.
  2. 코어 애니메이션 혹은 UIView 애니메이션 메소드를 사용해 애니메이션을 생성합니다.
  3. 전환을 정리하고 완료합니다.

Getting the Animation Parameters

animateTransition: 메소드에 의해 전달되는 맥락 전환 객체는 애니메이션을 수행할 때 사용하기 위한 데이터를 포함하고 있습니다. 맥락 전환 객체로부터 가장 최신의 정보를 가져올 수 있을 때, 뷰 컨트롤러로부터 캐시된 정보나 패치 정보를 사용하지 않아야 합니다. 뷰 컨트롤러를 제시하거나 해제하는 것은 간혹 뷰 컨트롤러 밖의 객체에 관여합니다. 예를 들어 커스텀 프리젠테이션 컨트롤러는 프리젠테이션의 부분으로써 백그라운드 뷰를 추가할 것입니다. 맥락 전환 객체는 추적인 뷰와 객체를 고려하여 애니메이션을 위한 정확한 뷰를 제공합니다.

  • 전환에 관여하는 뷰 컨트롤러의 "from"과 "to"를 가져오기 위해 viewControllerForKey: 메소드를 호출해야 합니다. 어떤 뷰 컨트롤러가 전환에 참여하고 있는지에 대해서 알고 있다곡 ㅏ정하지 않아야 합니다. UIKit은 새로운 특성 환경에 적응하거나 앱으로부터 요청에 반응하고 있는 동안 뷰 컨트롤러를 변경시킬 것입니다.
  • 애니메이션을 위한 슈퍼뷰를 가져오기 위해서 containerView 메소드를 호출해야 합니다. 이 뷰에 모든 핵심 하위뷰를 추가합니다. 예를 들어 프리젠테이션이 진행되는 동안 이 뷰에 제시되는 뷰 컨트롤러의 뷰를 추가합니다.
  • 추가되거나 삭제될 뷰를 가져오기 위해서 viewForKey: 메소드를 호출해야 합니다. 뷰 컨트롤러의 뷰는 전환이 일어나는 동안 추가되거나 제거되는 유일한 것이 아닙니다. viewForKey: 메소드는 추가하거나 제거해야 하는 데 필요한 모든 것을 포함하고 있는 루트 뷰를 반환합니다.
  • 추가되거나 제거되어야 하는 뷰에 대한 마지막 프레임 사각형을 가져오기 위해 finalFrameForViewController 메소드를 호출해야 합니다.

맥락 전환 객체는 "from과 "to" 명명법을 사용해 전환에 관여하는 뷰 컨트롤러, 뷰, 프레임 사각형을 확인합니다. "from" 뷰 컨트롤러는 항상 전환이 시작될 때 화면에 뷰가 표시되는 뷰 컨트롤러이며, "to" 뷰 컨트롤러는 전환이 끝날 때 뷰가 시각적인 상태로 되는 뷰 컨트롤러입니다. Figure 10-4에서 볼 수 있는 것처럼 "from"과 "to" 뷰 컨트롤러는 프리젠테이션과 해제 사이에서 위치를 바꿉니다.

Figure 10-4 The from and to objects

값을 바꾸는 것은 프리젠테이션과 해제를 처리하는 하나의 애니메이터를 작성하기에 쉬운 방법입니다. 애니메이터를 디자인할 때, 단지 해야 할 것은 프리젠테이션 혹은 해제를 애니메이션할지 여부에 대한 내용을 알 수 있는 속성을 포함하는 것입니다. 둘 사이의 차이는 아래와 같습니다.

  • 프리젠테이션의 경우 컨테이너 뷰 계층구조에 "to" 뷰를 추가합니다.
  • 해제의 경우 컨테이너 뷰 계층구조에서 "from" 뷰를 제거합니다.

Creating the Transition Animations

보통의 프리젠테이션이 진행되는 동안 제시되는 뷰 컨트롤러에 속하는 뷰는 애니메이션 움직임에 따라 위치에 놓여집니다. 다른 뷰는 프리젠테이션의 일부분으로써 애니메이션 동작으로 처리될 것입니다. 하지만 애니메이션의 주요 목표는 항상 뷰 계층구조에 추가되려고 하는 뷰입니다.

메인 뷰가 애니메이션 동작을 할 때, 애니메이션을 설정하고자 하는 기본적인 액션은 같습니다. 전환 맥락 객체로부터 필요한 객체와 데이터를 가져오고, 실제 애니메이션을 생성하기 위해 해당 정보를 사용하면 됩니다.

  • 프리젠테이션 애니메이션은 아래와 같은 동작을 합니다.

    • 전환에 관여하는 뷰 컨트롤러와 뷰를 회수하기 위해 viewControllerForKey:viewForKey: 메소드를 사용합니다.
    • "to" 뷰의 시작점을 설정합니다. 다른 모든 속성들에 시작값 또한 설정합니다.
    • 컨텍스트 전환 맥락의 finalFrameForViewController 메소드로부터 "to" 뷰의 마지막 위치를 가져옵니다.
    • 컨테이너 뷰의 하위뷰로써 "to" 뷰를 추가합니다.
    • 애니메이션을 생성합니다.
      • 애니메이션 블록 내부에 있는 컨테이너 뷰 속에 "to" 뷰를 마지막 위치로 애니메이션 처리해야 합니다.
      • 컴플리션 블록 내부에서 completeTransition: 메소드를 호출하고, 다른 모든 정리를 수행합니다.
  • 해제 애니메이션은 다음과 같은 동작을 합니다.

    • 전환에 관여하는 뷰 컨트롤러와 뷰를 회수하기 위해 viewControllerForKey:viewForKey: 메소드를 사용합니다.
    • "from" 뷰의 마지막 위치를 계산합니다. 이 뷰는 당장 해제되는 제시된 뷰 컨트롤러에 속합니다.
    • 컨테이너 뷰의 하위뷰로써 "to" 뷰를 추가합니다.
      프리젠테이션 진행 동안 제시하는 뷰 컨트롤러에 속하는 뷰는 전환이 완료될 때 제거됩니다. 결과적으로 해제 작업이 진행되는 동안 컨테이너에 다시. 뷰를 추가해야 합니다.
      • 애니메이션을 생성합니다.
        • 애니메이션 블록 내부에 컨테이너 뷰 속에서 "from" 뷰의 마지막 위치를 애니메이션 처리해야 합니다. 다른 모든 기타 속성들을 마지막 값으로 설정해야 합니다.
        • 컴플리션 블록에서 뷰 계층구조로부터 "from" 뷰를 제거해야 하고, completeTransition: 메소드를 호출해야 합니다. 필요한 경우 정리를 수행할 다른 것들을 수행해야 합니다.

Figure 10-5는 뷰를 대각선으로 애니메이션 처리하는 커스텀 프리젠테이션과 해제를 보여주고 있습니다. 프리젠테이션 진행 동안 제시되는 뷰는 화면 밖에서 애니메이션을 통해 대각선으로 등장하고, 왼쪽으로 시각화될 때까지 움직입니다. 해제가 일어나는 동안 뷰는 방향을 반대로 하고 아래 방향으로 애니메이션 되어 화면 밖으로 나갈 때까지 오른쪽으로 움직입니다.

Figure 10-5 A custom presentation and dismissal

Listing 10-2는 Figure 10-5에서 볼 수 있는 전환을 구현하는 방법을 보여주고 있습니다. 애니메이션에 필요한 객체를 회수한 이후 animateTransition: 메소드는 영향을 받는 뷰의 프레임 사각형을 계싼합니다. 프리젠테이션 진행 동안 제시된 뷰는 toView 변수에 의해 나타납니다. 해제에서 해제되는 뷰는 fromView 변수로 나타납니다. 제시 속성은 전환 딜리게이트가 애니메이터를 생성할 때 적합한 값으로 설정하는 애니메이션 객체에 대한 속성입니다.

Listing 10-2 Animations for implementing a diagonal presentation and dismissal

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    // Get the set of relevant objects.
    UIView *containerView = [transitionContext containerView];
    UIViewController *fromVC = [transitionContext
            viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC   = [transitionContext
            viewControllerForKey:UITransitionContextToViewControllerKey];
 
    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
 
    // Set up some variables for the animation.
    CGRect containerFrame = containerView.frame;
    CGRect toViewStartFrame = [transitionContext initialFrameForViewController:toVC];
    CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC];
    CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromVC];
 
    // Set up the animation parameters.
    if (self.presenting) {
        // Modify the frame of the presented view so that it starts
        // offscreen at the lower-right corner of the container.
        toViewStartFrame.origin.x = containerFrame.size.width;
        toViewStartFrame.origin.y = containerFrame.size.height;
    }
    else {
        // Modify the frame of the dismissed view so it ends in
        // the lower-right corner of the container view.
        fromViewFinalFrame = CGRectMake(containerFrame.size.width,
                                      containerFrame.size.height,
                                      toView.frame.size.width,
                                      toView.frame.size.height);
    }
 
    // Always add the "to" view to the container.
    // And it doesn't hurt to set its start frame.
    [containerView addSubview:toView];
    toView.frame = toViewStartFrame;
 
    // Animate using the animator's own duration value.
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         if (self.presenting) {
                             // Move the presented view into position.
                             [toView setFrame:toViewFinalFrame];
                         }
                         else {
                             // Move the dismissed view offscreen.
                             [fromView setFrame:fromViewFinalFrame];
                         }
                     }
                     completion:^(BOOL finished){
                         BOOL success = ![transitionContext transitionWasCancelled];
 
                         // After a failed presentation or successful dismissal, remove the view.
                         if ((self.presenting && !success) || (!self.presenting && success)) {
                             [toView removeFromSuperview];
                         }
 
                         // Notify UIKit that the transition has finished
                         [transitionContext completeTransition:success];
                     }];
 
}

Cleaning Up After the Animations

전환 애니메이션의 끝에서 completeTransition: 메소드를 호출하는 것은 중요합니다. 이 메소드를 호출하는 것을 통해 UIKit에게 전환이 완료되었으며, 사용자가 제시된 뷰 컨트롤러의 사용을 시작할 것이라는 내용을 알려줍니다. 또한, 이 메소드를 호출하는 것은 다른 컴플리션 핸들러의 캐스케이딩을 촉발합니다. 여기에는 presentViewController:animated:completion: 메소드와 애니메이터 객체의 animationEnded: 메소드가 포함되어 있습니다. completeTransition: 메소드를 호출할 최적의 장소는 애니메이션 블록의 컴플리션 핸들러 내부입니다.

전환이 취소될 수 있기 때문에 어떤 정리가 요구되는지를 결정하기 위한 컨텍스트 객체의 transitionWasCancelled 메소드 반환값을 사용해야 합니다. 프리젠테이션이 취소될 때 애니메이터는 뷰 계층구조에 스스로 만들어낸 모든 수정을 되돌려야 합니다. 성공적인 해제 역시 유사한 액션을 요구합니다.

Adding Interactivity to Your Transitions

애니메이션을 상호작용 하는 형태로 만드는 가장 쉬운 방법은 UIPercentDriveInteractiveTransition 객체를 사용하는 것입니다. UIPercentDrivenInteractiveTransition 객체는 애니메이션의 타이밍을 제어하기 위해 존재하는 애니메이션 객체와 함께 작동합니다. 이는 기존에 제공한 컴플리션 퍼센티지 값을 사용해 작동합니다. 단지 해야 하는 것은 컴플리션 퍼센티지 값과 새로운 이벤트 도착 시 컴플리션 퍼센티지 값을 업데이트 하는 계산에 요구되는 이벤트 처리 코드를 설정하는 것입니다.

UIPercentDrivenInteractiveTransition 클래스는 서브클래싱을 통해 사용하거나 서브클래싱 없이도 사용할 수 있습니다. 서브클래싱을 하면 서브클래스의 init 메소드(혹은 startInteractiveTansition: 메소드)를 사용해 이벤트 처리 코드의 설정을 수행할 수 있도록 해야 합니다. 이후 새 컴플리션 퍼센티지 값을 계산하기 위해 커스텀 이벤트 처리 코드를 사용해야 하고, updateInteractiveTransition: 메소드를 호출해야 합니다. 코드가 전환이 완료되어야 함을 결정하면, finishInteractiveTransition 메소드를 호출해야 합니다.

Listing 10-3은 UIPercentDrivenInteractiveTransition 서브클래스의 startInteractiveTransition: 메소드에 대한 커스텀 구현을 보여줍니다. 이 메소드는 터치 이벤트를 추적하기 위한 팬 제스쳐 리코그나이저를 설정하고, 애니메이션을 위해 컨테이너 뷰에 제스쳐 리코그나이저를 설치합니다. 또한, 이후 사용을 위한 전환 컨텍스트에 레퍼런스를 저장합니다.

Listing 10-3 Configuring a percent-driven interactive animator

- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
   // Always call super first.
   [super startInteractiveTransition:transitionContext];
 
   // Save the transition context for future reference.
   self.contextData = transitionContext;
 
   // Create a pan gesture recognizer to monitor events.
   self.panGesture = [[UIPanGestureRecognizer alloc]
                        initWithTarget:self action:@selector(handleSwipeUpdate:)];
   self.panGesture.maximumNumberOfTouches = 1;
 
   // Add the gesture recognizer to the container view.
   UIView* container = [transitionContext containerView];
   [container addGestureRecognizer:self.panGesture];
}

제스쳐 리코그나이저는 도착하는 새로운 이벤트마다 스스로 갖고 있는 액션 메소드를 호출합니다. 액션 메소드의 구현은 제스쳐가 성공했는지 실패했는지 혹은 아직 진행 중인지를 결정하기 위한 제스쳐 리코그나이저의 상태 정보를 사용할 수 있습니다. 동시에 제스쳐를 위한 새로운 퍼센티지 값을 계싼하기 위해 최신 터치 이벤트 정보를 사용할 수 있습니다.

Listing 10-4는 Listing 10-3에서 설정된 팬 제스쳐 리코그나이져로부터 호출되는 메소드를 보여줍니다. 새로운 이벤트가 도착하면 이 메소드는 애니메이션의 컴플리션 퍼센티지를 계싼하기 위해 수직 travel 거리를 사용합니다. 제스쳐가 끝나면 메소드는 전환을 마무리합니다.

Listing 10-4 Using events to update the animation progress

-(void)handleSwipeUpdate:(UIGestureRecognizer *)gestureRecognizer {
    UIView* container = [self.contextData containerView];
 
    if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
        // Reset the translation value at the beginning of the gesture.
        [self.panGesture setTranslation:CGPointMake(0, 0) inView:container];
    }
    else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
        // Get the current translation value.
        CGPoint translation = [self.panGesture translationInView:container];
 
        // Compute how far the gesture has travelled vertically,
        //  relative to the height of the container view.
        CGFloat percentage = fabs(translation.y / CGRectGetHeight(container.bounds));
 
        // Use the translation value to update the interactive animator.
        [self updateInteractiveTransition:percentage];
    }
    else if (gestureRecognizer.state >= UIGestureRecognizerStateEnded) {
        // Finish the transition and remove the gesture recognizer.
        [self finishInteractiveTransition];
        [[self.contextData containerView] removeGestureRecognizer:self.panGesture];
    }
}

NOTE
계산되는 값은 애니메이션의 전체 길이 중 컴플리션 퍼센티지를 보여줍니다. 상호작용 애니메이션의 경우 애니메이션에서 초기 속도, 증폭 값, 비선형적 컴플리션 커브와 같은 비선형적 효과를 피할 수 있도록 구현하길 원할 수 있습니다. 이와 같은 효과는 모든 기초 뷰의 움직임으로부터 이벤트의 터치 위치와 디커플링되는 경향이 있습니다.

Creating Animations that Run Alongside a Transition

전환에 연관된 뷰 컨트롤러는 모든 프리젠테이션 상단이나 전환 애니메이션에서 추가적인 애니메이션을 수행할 쑤 있습니다. 예를 들어 제시된 뷰 컨트롤러는 전환이 진행되는 동안 자신이 갖는 뷰 계층구조를 애니메이션 처리하고, 전환이 발생하는 동안 모션 효과나 다른 시각적 피드백을 추가할 것입니다. 모든 객체는 제시된 뷰 컨트롤러 혹은 제시하는 뷰 컨트롤러의 transitionCoordinator 프로퍼티에 접근할 수만 있다면 애니메이션을 생성할 수 있습니다. 전환 조정자는 전환이 진행되는 동안에만 존재합니다.

애니메이션을 생성하려면 전환 조정자의 animateAlongsideTransition:completion: 혹은 animateAlongsideTransitionInView:animation:completion: 메소드를 호출해야 합니다. 제공한 블록은 전환 애니메이션이 시작되기까지 저장되고, 이 시점에서 나머지 애니메이션과 함께 수행됩니다.

Using a Presentation Controller with Your Animations

커스텀 프리젠테이션의 경우 뷰 컨트롤러에 커스텀 모양을 갖출 수 있도록 프리젠테이션 컨트롤러를 제공할 수 있습니다. 프리젠테이션 컨트롤러는 뷰 컨트롤러와 컨텐츠로부터 분리된 모든 커스텀 크롬을 관리합니다. 예를 들어 뷰 컨트롤러의 뷰 뒤에 위치한 흐리게 처리된 뷰는 프리젠테이션 컨트롤러에 의해 관리될 것입니다. 특정 뷰 컨트롤러의 뷰를 관리하지는 않는다는 사실은 앱에서 모든 뷰 컨트롤러에 같은 프리젠테이션 컨트롤러를 사용할 수 있다는 것을 의미합니다.

제시된 뷰 컨트롤러의 전환 딜리게이트로부터 커스텀 프리젠테이션 컨트롤러를 제공할 수 있습니다. (뷰 컨트롤러의 modalTransitionStyle 속성은 UIModalPresentationCustom이 되어야 합니다.) 프리젠테이션 컨트롤러는 모든 애니메이터 객체를 병렬로 운영합니다. 애니메이터 객체가 뷰 컨트롤러의 뷰를 애니메이션을 통해 위치시킬 때 프리젠테이션 컨트롤러는 모든 추가적인 뷰를 애니메이션을 통해 위치시킵니다. 전환이 끝나는 시점에 프리젠테이션 컨트롤러는 뷰 컨트롤러에 모든 마지막 조정을 수행할 수 있는 기회를 얻습니다.

커스텀 프리젠테이션 컨트롤러를 생성하는 방법에 대한 더 많은 정보는 Creating Custom Presentations를 살펴보시기 바랍니다.

Creating Custom Presentations는 바로 아래에 나옵니다.

Creating Custom Presentations

UIKit은 뷰 컨트롤러의 컨텐트를 컨텐트가 화면에 제시되고 나타나는 방법으로부터 분리합니다. 제시된 컨트롤러는 뷰 컨트롤러의 뷰를 표시하기 위해 사용되는 시각적 스타일을 다루고 있는 기본 프리젠테이션 컨트롤러 객체에 의해 관리됩니다. 프리젠테이션 컨트롤러는 아래와 같은 작업을 수행할 것입니다.

  • 제시되는 뷰 컨트롤러의 크기를 설정합니다.
  • 제시된 컨텐트의 시각적 모양을 변경시키기 위해 커스텀 뷰를 추가합니다.
  • 모든 사용자 뷰에 대해 전환 애니메이션을 제공합니다.
  • 앱의 환경에 변경사항이 발생하면 프리젠테이션의 시각적 모양을 환경에 맞게 적용합니다.

UIKit은 표준 프리젠테이션 스타일을 위한 프리젠테이션 컨트롤러를 제공합니다. 뷰 컨트롤러의 프리젠테이션 스타일을 UIModalPresentationCustom으로 설정하고 적합한 전환 딜리게이트를 제공하는 경우 UIKit은 커스텀 프리젠테이션 컨트롤러를 사용하게 될 것입니다.

The Custom Presentation Process

프리젠테이션이 UIModalPResentationCustom인 뷰 컨트롤러를 제시할 때, UIKit은 프리젠테이션 프로세스를 관리하기 위해 커스텀 프리젠테이션 컨트롤러를 탐색합니다. 프리젠테이션이 처리될 때, UIKit은 프리젠테이션 컨트롤러의 메소드를 하출합니다. 이는 모든 커스텀 뷰를 설정하고 이 뷰를 적합한 위치에 애니메이션으로 놓을 수 있는 기회를 제공하게 됩니다.

프리젠테이션 컨트롤러는 전체 전환을 구현하기 위해 모든 애니메이터 객체와 함께 작동합니다. 애니메이터 객체는 뷰 컨트롤러의 컨텐츠를 애니메이션을 통해 스크린으로 넣고, 프리젠테이션 컨트롤러가 모든 것을 처리합니다. 프리젠테이션 컨트롤러는 갖고 있는 뷰를 애니메이션 처리합니다. 프리젠테이션 컨트롤러의 presentedView 메소드를 오버라이드 할 수도 있고, 애니메이터 객체들이 이와 같은 뷰 모두 혹은 몇몇을 애니메이션 처리하도록 할수도 있습니다.

프리젠테이션이 진행되는 동안 UIKit은 아래 작업을 수행합니다.

  1. 커스텀 프리젠테이션 컨트롤러를 회수하기 위해 전환 딜리게이트의 presentationControllerForPresentedViewController:presentingViewController:sourceViewController: 메소드를 호출합니다.
  2. 애니메이터와 상호작용 애니메이터 객체에 대한 전환 딜리게이트를 요청을 합니다.
  3. 프리젠테이션 컨트롤러의 presentationTransitionWillBegin 메소드를 호출합니다.
    이 메소드에 대한 구현은 뷰 계층구조에 모든 커스텀 뷰를 추가해야만 하고, 이 뷰들에 대한 애니메이션을 설정해줘야 합니다.
  4. 프리젠테이션 컨트롤러로부터 presentedView를 가져옵니다.
    이 메소드에 의해 반환된 뷰는 애니메이터 객체에 의해 애니메이션 움직임으로 위치에 놓이게 됩니다. 이 메소드는 제시된 뷰 컨트롤러의 루트 뷰를 반환합니다. 프리젠테이션 컨트롤러는 해당 뷰를 커스텀 백그라운드 뷰로 대체하도록 할 수도 있습니다 다른 뷰를 구체화하면, 뷰 계층구조에 제시된 뷰 컨트롤러의 루트 뷰를 끼워넣어야 합니다.
  5. 전환 애니메이션을 수행합니다.
    전환 애니메이션은 애니메이터 객체에 의해 생성된 주요 애니메이션을 포함하고, 주요 애니메이션과 함께 작동하는 직접 설정한 모든 애니메이션을 포함합니다. 전환 애니메이션과 관련한 더 많은 정보는 The Transiotion Animation Sequence를 살펴보시기 바랍니다.
    애니메이션이 처리되는 동안 UIKit은 필요한 경우 커스텀 뷰의 레이아웃을 조정할 수 있도록 프리젠테이션 컨트롤러의 containerViewWillLayoutSubviews containerViewDidLayoutSubviews 메소드를 호출합니다.
  6. 전환 애니메이션이 마무리될 때 presentationTransitionDidEnd 메소드를 호출합니다.

해제가 진행되는 동안 UIKit은 아래 작업을 수행합니다.

  1. 현재 시각화된 뷰 컨트롤러로부터 커스텀 프리젠테이션 컨트롤러를 가져옵니다.
  2. 애니메이터와 상호작용 애니메이터 객체에 대한 전환 딜리게이트를 요청합니다.
  3. 프리젠테이션 컨트롤러의 dismissalTransitionWillBegin 메소드를 호출합니다.
    이 메소드에 대한 구현은 뷰 계층구조에 모든 커스텀 뷰를 추가해야만 하고, 해당하는 뷰들에 애니메이션을 설정해줘야 합니다.
  4. 프리젠테이션 컨트롤러로부터 presentedView를 가져옵니다.
  5. 전환 애니메이션을 수행합니다.
    전환 애니메이션은 애니메이터 객체에 의해 생성된 주요 애니메이션을 포함하고, 주요 애니메이션과 함께 작동되도록 설정된 모든 애니메이션을 포함합니다. 전환 애니메이션에 관한 더 많은 정보는 Transition Animation Sequence를 살펴보시기 바랍니다.
    애니메이션이 처리되는 동안 UIKit은 모든 커스텀 제약들을 제거할 수 있도록 프리젠테이션 컨트롤러의 containerViewWillLayoutSubViewscontainerViewDidLayoutSubviews 메소드를 호출합니다.
  6. 전환 애니메이션이 마무리되면 dismissalTransitionDidEnd: 메소드를 호출합니다.

Transition Animation Sequence는 윗부분에 등장합니다.

프리젠테이션이 처리되는 동안 프리젠젠테이션 컨트롤러의 frameOfPresentedViewInContainerViewpresentedView 메소드가 몇 번에 걸쳐 호출될 것입니다. 그렇기 때문에 구현은 빠르게 반환되어야 합니다. 또한, presentedView 메소드 구현은 뷰 계층구조 설정을 시도해서는 안 됩니다. 뷰 계층구조는 메소드가 호출되기 전에 설정되어 있어야 합니다.

Creating a Custom Presentation Controller

커스텀 프리젠테이션 스타일을 구현하려면 UIPresentationController를 서브클래싱하고, 프리젠테이션을 위한 뷰와 애니메이션을 생성하는 코드를 추가해야 합니다. 커스텀 프리젠테이션 컨트롤러를 생성할 때, 아래 질문사항을 고려하시기 바랍니다.

  • 어떤 뷰를 추가하고 싶은지 생각해야 합니다.
  • 추가적인 모든 뷰에 대한 애니메이션을 어떻게 하길 원하는지 고려해야 합니다.
  • 제시되는 뷰 컨트롤러의 크기가 어느 정도 되는지 생각해야 합니다.
  • 수평 regular 크기와 수평 compact 크기 클래스에서 프리젠테이션이 어떻게 적용되어야 하는지 생각해야 합니다.
  • 프리젠테이션이 완료될 때 뷰 컨트롤러르 제시하는 뷰 컨트롤러의 뷰가 제거되어야 하는지 결정해야 합니다.

이와 같은 의사결정은 UIPresentationController 클래스에 속한 각각의 다른 메소드를 오버라이드해서 원하는 형태를 적용해야 합니다.

Setting the Frame of the Presented View Controller

사용 가능한 공간의 부분만을 채우길 원한다면 제시되는 뷰 컨트롤러의 프레임 사각형을 수정할 수도 있습니다. 기본값으로 제시되는 뷰 컨트롤러는 컨테이너 뷰의 프레임을 완전히 채우도록 크기가 적용됩니다. 프레임 사각형을 변경시키려면 프리젠테이션 컨트롤러의 frameOfPresentedViewInContainerView 메소드를 오버라이드해야 합니다. Listing 11-1은 프레임이 오직 컨테이너 뷰의 오른쪽 절반만 덮도록 변경된 프레임을 보여주는 예시입니다. 이 경우 프리젠테이션 컨트롤러는 컨테이너의 나머지 절반을 채우기 위해 백그라운드 흐린 뷰를 사용합니다.

Listing 11-1 Changing the frame of a presented view controller

- (CGRect)frameOfPresentedViewInContainerView {
    CGRect presentedViewFrame = CGRectZero;
    CGRect containerBounds = [[self containerView] bounds];
 
    presentedViewFrame.size = CGSizeMake(floorf(containerBounds.size.width / 2.0),
                                         containerBounds.size.height);
    presentedViewFrame.origin.x = containerBounds.size.width -
                                    presentedViewFrame.size.width;
    return presentedViewFrame;
}

Managing and Animating Custom Views

커스텀 프리젠테이션은 제시되는 컨텐트에 커스텀 뷰를 추가하는 데 관여합니다. 시각적 장식을 순수하게 구현하기 위해 커스텀 뷰를 사용하거나 프리젠테이션에 실용적인 동작을 추가하기 위해 커스텀 뷰를 사용하시기 바랍니다. 예를 들어 백그라운드 뷰는 제시되는 컨텐트의 영역 밖에서 특정 액션을 추적하기 위해 제스쳐 리코그나이저를 통합할 것입니다.

프리젠테이션 컨트롤러는 프리젠테이션에 연관된 모든 커스텀 뷰를 생성하고 관리할 책임이 있습니다. 보통 프리젠테이션 컨트롤러의 초기화 동안 커스텀 뷰를 생성합니다. Listing 11-2는 흐린 뷰를 생성하는 커스텀 뷰 컨트롤러의 초기화 메소드를 보여줍니다. 이 메소드는 뷰를 생성하고 몇 가지 최소한의 설정을 수행합니다.

Listing 11-2 Initializing the presentation controller

- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController
                    presentingViewController:(UIViewController *)presentingViewController {
    self = [super initWithPresentedViewController:presentedViewController
                         presentingViewController:presentingViewController];
    if(self) {
        // Create the dimming view and set its initial appearance.
        self.dimmingView = [[UIView alloc] init];
        [self.dimmingView setBackgroundColor:[UIColor colorWithWhite:0.0 alpha:0.4]];
        [self.dimmingView setAlpha:0.0];
    }
    return self;
}

presentationTransitionWillBegin 메소드르 사용해 화면에 커스텀 뷰를 애니메이션으로 나타나도록 할 수 있습니다. 이 메소드에서 커스텀 뷰를 설정하고 컨테이너 뷰에 추가할 수 있도록 해야 합니다. Listing 11-3에서 보이는 것이 해당하는 예시입니다. 제시되는 뷰 혹은 제시하는 뷰 컨트롤러 모두에서 관련이 있는 모든 애니메이션을 생성하기 위해 전환 조정자를 사용해야 합니다. 제시되는 뷰 컨트롤러의 뷰를 이 메소드 안에서 수정하지 않아야 합니다. 애니메이터 객체는 제시되는 뷰 컨트롤러가 frameOfPresentedViewInContainerView 메소드로부터 반환되는 프레임 사각형으로 애니메이션 처리할 책임이 있습니다.

Listing 11-3 Animating the dimming view onto the screen

- (void)presentationTransitionWillBegin {
    // Get critical information about the presentation.
    UIView* containerView = [self containerView];
    UIViewController* presentedViewController = [self presentedViewController];
 
    // Set the dimming view to the size of the container's
    // bounds, and make it transparent initially.
    [[self dimmingView] setFrame:[containerView bounds]];
    [[self dimmingView] setAlpha:0.0];
 
    // Insert the dimming view below everything else.
    [containerView insertSubview:[self dimmingView] atIndex:0];
 
    // Set up the animations for fading in the dimming view.
    if([presentedViewController transitionCoordinator]) {
        [[presentedViewController transitionCoordinator]
               animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>
                                            context) {
            // Fade in the dimming view.
            [[self dimmingView] setAlpha:1.0];
        } completion:nil];
    }
    else {
        [[self dimmingView] setAlpha:1.0];
    }
}

프리젠테이션 완료 시점에 프리젠테이션의 취소에 의한 모든 정리과정을 처리할 수 있도록 presentationTransitionDidEnd: 메소드를 사용하시기 바랍니다. 상호작용 애니메이터 객체는 이 객체의 임계값 조건이 충족되지 않을 경우 전환을 취소할 것입니다. 이와 같은 일이 발생하면, 프리젠테이션 시작 시점에 추가한 모든 커스텀 뷰를 제거해야 하고, 다른 뷰들에 대해 이전 설정 형태로 반환해야 합니다. Listing 11-4에 보여지는 것과 같습니다.

Listing 11-4 Handling a cancelled presentation

- (void)presentationTransitionDidEnd:(BOOL)completed {
    // If the presentation was canceled, remove the dimming view.
    if (!completed)
        [self.dimmingView removeFromSuperview];
}

뷰 컨트롤러가 해제될 때, dismissalTransitionDidEnd: 메소드를 사용해 뷰 계층구조로부터 커스텀 뷰가 제거될 수 있도록 해야 합니다. 뷰의 사라지는 모습이 애니메이션으로 작동하길 원한다면, dismissalTransitionDidEnd: 메소드에 해당 애니메이션을 설정해야 합니다. Listing 11-5는 이전 예시에서 흐린. 뷰를 제거하는 메소드의 구현을 보여줍니다. 해제가 성공했는지 최소되었는지를 확인할 수 있도록 항상 dismissalTransitionDidEnd: 메소드의 파라미터를 확인해야 합니다.

Listing 11-5 Dismissing the presentation’s views

- (void)dismissalTransitionWillBegin {
    // Fade the dimming view back out.
    if([[self presentedViewController] transitionCoordinator]) {
        [[[self presentedViewController] transitionCoordinator]
           animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>
                                        context) {
            [[self dimmingView] setAlpha:0.0];
        } completion:nil];
    }
    else {
        [[self dimmingView] setAlpha:0.0];
    }
}
 
- (void)dismissalTransitionDidEnd:(BOOL)completed {
    // If the dismissal was successful, remove the dimming view.
    if (completed)
        [self.dimmingView removeFromSuperview];
}

Vending Your Presentation Controller to UIKit

뷰 컨트롤러를 제시할 때, 뷰 컨트롤러를 표시하기 위해 커스텀 프리젠테이션 컨트롤러를 사용해서 아래 작업을 수행해야 합니다.

  • 제시되는 뷰 컨트롤러의 modalPresentationStyle 속성에 UIModalPResentationCustom을 설정해야 합니다.
  • 제시되는 뷰 컨트롤러의 transitioningDelegate 속성에 전환 딜리게이트를 할당해야 합니다.
  • 전환 딜리게이트의 presentationControllerForPresentedViewController:presentingViewController:sourceViewController: 메소드를 구현해야 합니다.

UIKit은 전환 딜리게이트가 프리젠테이션 컨트롤러를 요구할 때 전환 딜리게이트의 presentationControllerForPresentedViewController:presentingViewController:sourceViewController: 메소드를 호출합니다. 이 메소드의 구현은 Listing 11-6에 보이는 것처럼 간단합니다. 프리젠테이션 컨트롤러를 생성하고 설정한 뒤 반환하면 됩니다. 이 메소드로부터 nil을 반환하면, UIKit은 전체 화면 프리젠테이션 스타일을 사용해 뷰 컨트롤러를 제시합니다.

Listing 11-6 Creating a custom presentation controller

- (UIPresentationController *)presentationControllerForPresentedViewController:
                                 (UIViewController *)presented
        presentingViewController:(UIViewController *)presenting
            sourceViewController:(UIViewController *)source {
 
    MyPresentationController* myPresentation = [[MyPresentationController]
       initWithPresentedViewController:presented presentingViewController:presenting];
 
    return myPresentation;
}

Adapting to Different Size Classes

프리젠테이션이 화면에 나타나는 동안 UIKit은 기본 특성의 변경이나 컨테이너 뷰의 크기가 변경될 때 프리젠테이션 컨트롤러에게 알려줍니다. 이와 같은 변경사항은 기기 회전이 발생하는 경우에 발생합니다. 프리젠테이션의 커스텀 뷰를 조정하거나 프리젠테이션 스타일을 업데이트하기 위해 특성 노티피케이션과 크기 노티피케이션을 사용할 수 있습니다.

새로운 특성과 크기를 적용하는 방법에 대한 더 많은 정보는 Building an Adaptive Interface를 살펴보시기 바랍니다.

Building an Adaptive Interface는 다음글에 나옵니다.

0개의 댓글