https://www.objc.io/issues/12-animations/custom-container-view-controller-transitions/


5 호 문제 에서 Chris Eidhof 는 iOS 7의 새로운 View Controller Transitions 을 보았습니다 . 결론은 다음과 같습니다 (강조).

우리는 네비게이션 컨트롤러에서 두 개의 뷰 컨트롤러 사이를 애니메이션으로 보았을 뿐이지 만 탭 바 컨트롤러 나 사용자 지정 컨테이너 뷰 컨트롤러에 대해서도 똑같이 할 수 있습니다 ...

기술적으로 사용자 지정 포함에서 두보기 컨트롤러 간의 전환을 사용자 지정할 수 있지만 iOS 7 API를 사용하는 경우 기본적으로 지원되지 않습니다. 이기는 커녕.

UITabBarController 또는 UINavigationController 하위 클래스가 아닌 UIViewController 직접 하위 클래스로 사용자 지정 컨테이너보기 컨트롤러에 대해 설명합니다.

임의의 애니메이션 컨트롤러 가 자식보기 컨트롤러 중 하나에서 대화식 또는 비 대화식으로 자동으로 전환을 수행 할 수 있도록 사용자 정의 컨테이너 UIViewController 하위 클래스에 사용할 수있는 API가 없습니다. 나는 그것을지지하려는 애플의 의도조차 아니라고 말하고 싶다. 다음 전환이 지원됩니다.

  • 탐색 컨트롤러 푸시 및 팝
  • 탭 막대 컨트롤러 선택 변경
  • 모달 발표 및 해고

이 장에서는 타사 애니메이션 컨트롤러를 지원하면서 사용자 정의 컨테이너 뷰 컨트롤러를 직접 만드는 방법을 보여줍니다.

iOS 5에서 도입 된 View Controller Containment 를 브러시로 처리해야하는 경우 Ricky Gregersen 의 " View Controller Containment "를 꼭 읽으십시오.

시작하기 전에

이 시점에서 한두 가지 질문을 할 수 있으므로 답변 해 드리겠습니다.

UINavigationController 또는 UITabBarController 서브 클래스 화하고 무료로 지원하는 것이 어떨까요?

글쎄, 가끔은 네가 원하는게 아니야. 어쩌면 이러한 클래스가 제공하는 것에서 멀리 떨어져있는 매우 특정한 모양이나 동작을 원할 수 있으므로 까다로운 해킹에 의존해야하며 프레임 워크의 새로운 버전과 충돌 할 위험이 있습니다. 아니면 단지 봉쇄를 완전히 통제하고 특수 기능을 지원하지 않아도되기를 원할 것입니다.

OK, 그렇다면 왜 그냥 transitionFromViewController:toViewController:duration:options:animations:completion:사용하지 않았습니까?

또 다른 좋은 질문입니다. 단지 그렇게하고 싶을 수도 있습니다. 하지만 코드를 신경 쓰고 전환을 캡슐화하려고합니다. 그렇다면 지금은 잘 입증 된 디자인 패턴을 사용하는 것이 어떻습니까? 또한 보너스로 무료로 제공되는 타사 전환 애니메이션을 지원합니다.

API 소개

이제 코딩을 시작하기 전에 잠시 후, 약속 드리겠습니다. 장면을 설정하십시오.

iOS 7 사용자 지정보기 컨트롤러 전환 API의 구성 요소는 대개 사용자가 기존 클래스 계층에 쉽게 연결할 수 있기 때문에 매우 유연한 프로토콜입니다. 5 가지 주요 구성 요소는 다음과 같습니다.

  1. 애니메이션 컨트롤러 는 UIViewControllerAnimatedTransitioning 프로토콜을 UIViewControllerAnimatedTransitioning 실제 애니메이션을 수행합니다.

  2. 상호 작용 컨트롤러 는 UIViewControllerInteractiveTransitioning 프로토콜을 준수하여 대화식 전환을 제어합니다.

  3. 전환 대리자 는 수행 할 전환 유형에 따라 애니메이션 및 상호 작용 컨트롤러를 편리하게 판매합니다.

  4. 전환 (Transitioning) 전환에 참여하는 뷰 컨트롤러 및 뷰의 속성과 같은 전환에 대한 메타 데이터를 정의하는 컨텍스트 입니다. 이러한 객체는 UIViewControllerContextTransitioning 프로토콜을 UIViewControllerContextTransitioning 시스템에서 만들고 제공합니다 .

  5. 트랜지션 애니메이션과 함께 다른 애니메이션을 실행하는 메소드를 제공하는 트랜지션 코디네이터 . UIViewControllerTransitionCoordinator 프로토콜을 준수합니다.

아시다시피,이 간행물을 읽지 않으면 대화식 및 비 대화식 전환이 있습니다. 이 기사에서는 비대화 형 전환에 집중할 것입니다. 이들은 가장 간단하므로 시작할 수있는 좋은 곳입니다. 즉, 위의 목록에서 애니메이션 컨트롤러 , 대리자 전환 및 컨텍스트 전환에 대해 설명합니다.

얘기 좀하자, 우리 손을 더러워 지자 ...

프로젝트

3 단계에서는 사용자 정의 하위보기 컨트롤러 전환 애니메이션에 대한 지원을 구현하는 사용자 정의 컨테이너보기 컨트롤러가 포함 된 샘플 앱을 작성합니다.

Xcode 프로젝트는 3 단계 로 GitHub 의 저장소에 저장됩니다 .

1 단계 : 기본 사항

우리의 애플 리케이션의 중앙 클래스는 ContainerViewController 입니다. 우리의 경우, 사소한 ChildViewController 객체 인 UIViewController 인스턴스의 배열을 호스트 ChildViewController . 컨테이너 뷰 컨트롤러는 각 하위 뷰 컨트롤러를 나타내는 탭 가능한 아이콘으로 개인 하위 뷰를 설정합니다.

1 단계 : 애니메이션 없음

하위보기 컨트롤러간에 전환하려면 아이콘을 누릅니다. 이 단계에서는 하위 뷰 컨트롤러를 전환 할 때 전환 애니메이션이 없습니다.

1 단계 태그를 확인하여 기본 앱의 코드를 확인하세요.

2 단계 : 전환 애니메이션하기

전환 애니메이션을 추가 할 때 UIViewControllerAnimatedTransitioning 준수하는 애니메이션 컨트롤러 를 지원하고자합니다. 프로토콜은 다음 세 가지 방법을 정의합니다. 처음 두 가지 방법이 필요합니다.

 - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext; - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext; - (void)animationEnded:(BOOL)transitionCompleted; 

이것은 우리가 알아야 할 모든 것을 말해줍니다. 컨테이너 뷰 컨트롤러가 애니메이션을 수행하려고 할 때 애니메이션 컨트롤러에 해당 기간 동안 쿼리하고 실제 애니메이션을 수행하도록 요청할 수 있습니다. 완료되면, 애니메이션 컨트롤러에animationEnded: 를 호출 할 수 있습니다 (해당 옵션 메소드가 구현 된 경우).

그러나 먼저 알아야 할 것이 하나 있습니다. 위의 메서드 시그니처에서 볼 수 있듯이 두 개의 필수 메서드는 UIViewControllerContextTransitioning 준수하는 컨텍스트 매개 변수를 사용합니다. 일반적으로 내장 클래스를 사용할 때 프레임 워크는이 컨텍스트를 만들어 애니메이션 컨트롤러에 전달합니다. 그러나 우리의 경우 우리가 프레임 워크의 역할을하기 때문에 우리는 그 객체를 생성해야합니다.

이것은 프로토콜의 과도한 사용의 편의가 오는 곳입니다. 분명히 no-go 인 개인 클래스를 재정의해야하는 대신, 우리는 스스로를 만들 수 있고 문서화 된 프로토콜을 따르도록 할 수 있습니다.

그러나 방법 이 많이 있지만 모두 필요합니다. 그러나 우리는 현재 비 대화 형 전환을 지원하기 때문에 지금은 일부만 무시할 수 있습니다.

UIKit과 마찬가지로 private NSObject <UIViewControllerContextTransitioning> 클래스를 정의합니다. 우리의 특수한 경우에는 PrivateTransitionContext 클래스이며 이니셜 라이저는 다음과 같이 구현됩니다.

 - (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight { NSAssert ([fromViewController isViewLoaded] && fromViewController.view.superview, @"The fromViewController view must reside in the container view upon initializing the transition context."); if ((self = [super init])) { self.presentationStyle = UIModalPresentationCustom; self.containerView = fromViewController.view.superview; self.viewControllers = @{ UITransitionContextFromViewControllerKey:fromViewController, UITransitionContextToViewControllerKey:toViewController, }; CGFloat travelDistance = (goingRight ? -self.containerView.bounds.size.width : self.containerView.bounds.size.width); self.disappearingFromRect = self.appearingToRect = self.containerView.bounds; self.disappearingToRect = CGRectOffset (self.containerView.bounds, travelDistance, 0); self.appearingFromRect = CGRectOffset (self.containerView.bounds, -travelDistance, 0); } return self; } 

우리는 기본적으로 출현하는 장면과 사라지는 장면에 대해 초기 프레임과 최종 프레임을 포함한 상태를 캡처합니다.

공지 사항에서 우리의 이니셜 라이저는 우리가 올바르게 가고 있는지 아닌지에 대한 정보를 필요로합니다. 버튼이 서로 옆에 수평으로 배치되는 우리의 전문화 된 ContainerViewController 컨텍스트에서 전환 컨텍스트는 각 프레임을 설정하여 위치 관계에 대한 정보를 기록합니다. 애니메이션 컨트롤러 또는 애니메이터 는 애니메이션을 작성할 때 이것을 사용하도록 선택할 수 있습니다.

이 정보를 다른 방법으로 수집 할 수는 있지만 애니메이터가ContainerViewController 와 뷰 컨트롤러에 대해 알 필요가 있으며이를 원하지 않습니다. 애니메이터는 전달 된 컨텍스트에만 관심을 가져야합니다. 애니메이터가 다른 컨텍스트에서 재사용 할 수 있기 때문에 이상적입니다.

우리 자신의 애니메이션 컨트롤러를 다음으로 만들 때 이것을 염두에 두어야합니다. 이제 우리는 전환 컨텍스트를 사용할 수있게되었습니다.

이것이 바로 View Controller Transitions ( 문제 5 번) 에서했던 것과 정확히 일치한다는 것을 기억하십시오. 그럼 그냥 사용하지 않는 이유는 무엇입니까? 실제로이 프레임 워크에서 프로토콜을 광범위하게 사용하기 때문에 Animator 클래스 인 Animator클래스를 해당 프로젝트에서 가져 와서 수정없이 바로 연결할 수 있습니다.

Animator 인스턴스를 사용하여 전환을 애니메이션화하는 것은 기본적으로 다음과 같습니다.

 [fromViewController willMoveToParentViewController:nil]; [self addChildViewController:toViewController]; Animator *animator = [[Animator alloc] init]; NSUInteger fromIndex = [self.viewControllers indexOfObject:fromViewController]; NSUInteger toIndex = [self.viewControllers indexOfObject:toViewController]; PrivateTransitionContext *transitionContext = [[PrivateTransitionContext alloc] initWithFromViewController:fromViewController toViewController:toViewController goingRight:toIndex > fromIndex]; transitionContext.animated = YES; transitionContext.interactive = NO; transitionContext.completionBlock = ^(BOOL didComplete) { [fromViewController.view removeFromSuperview]; [fromViewController removeFromParentViewController]; [toViewController didMoveToParentViewController:self]; }; [animator animateTransition:transitionContext]; 

이것의 대부분은 필요한 콘테이너 뷰 컨트롤러 노래와 춤이고, 우리가 왼쪽으로 가든 오른쪽으로 가야 하는지를 알아내는 것입니다. 애니메이션을 만드는 것은 기본적으로 3 가지 코드 라인입니다 : 1) 애니메이터 생성, 2) 전환 컨텍스트 생성, 3) 애니메이션 트리거.

이것으로 전환은 이제 다음과 같이 보입니다.

2 단계 : 타사 애니메이션

정말 멋진. 우리는 애니메이션 코드를 직접 작성하지도 않았습니다!

이것은 stage-2 태그가있는 코드에 반영됩니다. 2 단계 변경 사항 전체를 보려면 1 단계와 비교해 diff를 확인하십시오.

3 단계 : 수축 포장

우리가해야 할 마지막 한 가지는 ContainerViewController 축소 포장하는 것입니다.

  1. 자체 기본 전환 애니메이션이 포함되어 있습니다.
  2. 대체 애니메이션 컨트롤러를 판매하는 대리인을 지원합니다.

이는 대리자 프로토콜을 생성 할뿐만 아니라 Animator 클래스에 대한 종속성을 편리하게 제거하는 것을 수반합니다.

프로토콜을 다음과 같이 정의합니다.

 @protocol ContainerViewControllerDelegate <NSObject> @optional - (void)containerViewController:(ContainerViewController *)containerViewController didSelectViewController:(UIViewController *)viewController; - (id <UIViewControllerAnimatedTransitioning>)containerViewController:(ContainerViewController *)containerViewController animationControllerForTransitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController; @end 

containerViewController:didSelectViewController: 메소드는 ContainerViewController 를보다 완벽한 기능의 애플리케이션에 쉽게 통합 할 수있게 해줍니다.

흥미로운 방법은 containerViewController:animationControllerForTransitionFromViewController:toViewController:물론 UIKit의 다음 컨테이너 뷰 컨트롤러 위임 프로토콜 메서드와 비교할 수 있습니다.

  • tabBarController:animationControllerForTransitionFromViewController:toViewController:UITabBarControllerDelegate )
  • navigationController:animationControllerForOperation:fromViewController:toViewController:UINavigationControllerDelegate )

이러한 모든 메서드는 id<UIViewControllerAnimatedTransitioning> 개체를 반환합니다.

Animator 객체를 항상 사용하는 대신, 위임자에게 애니메이션 컨트롤러를 요청할 수 있습니다.

 id<UIViewControllerAnimatedTransitioning>animator = nil; if ([self.delegate respondsToSelector:@selector (containerViewController:animationControllerForTransitionFromViewController:toViewController:)]) { animator = [self.delegate containerViewController:self animationControllerForTransitionFromViewController:fromViewController toViewController:toViewController]; } animator = (animator ?: [[PrivateAnimatedTransition alloc] init]); 

대리자가 있고 애니메이터를 반환하면 사용할 것입니다. 그렇지 않으면 PrivateAnimatedTransition 클래스의 자체 기본 애니메이터를 PrivateAnimatedTransition . 우리는 이것을 다음에 구현할 것이다.

기본 애니메이션은 Animator 애니메이션과 약간 다르지만 코드는 놀라 울 정도로 비슷합니다. 다음은 전체 구현입니다.

 @implementation PrivateAnimatedTransition static CGFloat const kChildViewPadding = 16; static CGFloat const kDamping = 0.75f; static CGFloat const kInitialSpringVelocity = 0.5f; - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext { return 1; } - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; // When sliding the views horizontally, in and out, figure out whether we are going left or right. BOOL goingRight = ([transitionContext initialFrameForViewController:toViewController].origin.x < [transitionContext finalFrameForViewController:toViewController].origin.x); CGFloat travelDistance = [transitionContext containerView].bounds.size.width + kChildViewPadding; CGAffineTransform travel = CGAffineTransformMakeTranslation (goingRight ? travelDistance : -travelDistance, 0); [[transitionContext containerView] addSubview:toViewController.view]; toViewController.view.alpha = 0; toViewController.view.transform = CGAffineTransformInvert (travel); [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:kDamping initialSpringVelocity:kInitialSpringVelocity options:0x00 animations:^{ fromViewController.view.transform = travel; fromViewController.view.alpha = 0; toViewController.view.transform = CGAffineTransformIdentity; toViewController.view.alpha = 1; } completion:^(BOOL finished) { fromViewController.view.transform = CGAffineTransformIdentity; [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; }]; } @end 

뷰 프레임이 위치 관계를 반영하도록 설정되지 않은 경우에도 항상 같은 방향으로 전환되지만 코드는 여전히 작동합니다. 따라서이 클래스는 다른 코드베이스에서 계속 사용할 수 있습니다.

전환 애니메이션은 이제 다음과 같이 보입니다.

3 단계 : 타사 애니메이션

stage-3 태그가있는 코드에서 기본 애니메이션이 작동하는지 보려면 앱 대리인의 대리자 설정이 주석 처리 되었습니다. Animator 다시 사용하려면 다시 설정하십시오. 2 단계에 대한 전체 비교 를 확인하고 싶을 수 있습니다.

개발자가 자신의 iOS 7 사용자 정의 애니메이션 컨트롤러 ( UIViewControllerAnimatedTransitioning ) 객체로 오버라이드 할 수있는 멋지게 애니메이션 된 기본 전환 기능이 포함 된 자체 ContainerViewController 가 있습니다. 심지어 소스 코드에 액세스 할 필요도 없습니다.

결론

이 기사에서는 iOS 7의 새로운 Custom View Controller Transitions과 통합하여 맞춤 컨테이너 뷰 컨트롤러를 일류 UIKit 시민으로 만드는 방법에 대해 살펴 보았습니다.

즉, 비대화 형 전환 애니메이션을 사용자 정의 컨테이너보기 컨트롤러에 적용 할 수 있습니다. 7 가지 문제에서 기존의 전환 클래스를 가져 와서 수정할 필요가 없기 때문에이를 보았습니다.

사용자 정의 컨테이너 뷰 컨트롤러를 라이브러리 또는 프레임 워크의 일부로 배포하거나 코드를 다시 사용할 수있게하려는 경우에 적합합니다.

지금까지는 비대화 형 전환 만 지원합니다. 다음 단계는 대화 형 전환을 지원하는 것입니다.

나는 그것을 당신을위한 운동으로 남겨 둘 것입니다. 기본적으로 프레임 워크 동작을 모방하기 때문에 다소 복잡합니다. 실제로 모든 추측입니다.

업데이트 : Alek Åström 은 신속하게 도전 과제를 수행하고 " 대화 형 사용자 정의 컨테이너보기 컨트롤러 전환 "이라는 매우 흥미로운 기사를 게시했습니다. 추가 보너스로, 그것은 새로운 운동을 포함 ...