如何为自定义容器控制器转换“取消”视图外观转换
我创build了一个类似于UIPageViewController
的自定义容器控制器,以便我可以实现一些自定义转换和数据源逻辑。 我试图模仿新的客户视图控制器转换API在iOS 7中的工作方式,除了在取消转换时使用视图外观callback时出现的一些恼人的怪癖时,它运行良好。
也就是说,在进行转换的时候,究竟应该在什么时候开始出现转换beginAppearanceTransition:animated:
和endAppearanceTransition
被调用?
我的自定义容器类有这样的代码:
- (BOOL)shouldAutomaticallyForwardAppearanceMethods { return NO; // Since the automatic callbacks are wrong for our custom transition. } - (void)startTransition:(CustomTransitionContext *)context { // Get reference to the view controllers involved in the transition. UIViewController *oldVC = [context viewControllerForKey:UITransitionContextFromViewController]; UIViewController *newVC = [context UITransitionContextToViewController]; // Prepare parent/child relationship changes. [oldVC willMoveToParentViewController:nil]; [self addChildViewController:newVC]; // Begin view appearance transitions. [oldVC beginAppearanceTransition:NO animated:[context isAnimated]]; [newVC beginAppearanceTransition:YES animated:[context isAnimated]]; // Register a completion handler to run when the context's completeTransition: method is called. __weak CustomContainerController *weakSelf = self; context.transitionCompletionHandler = ^(BOOL complete) { // End appearance transitions here? [oldVC endAppearanceTransition]; [newVC endAppearanceTransition]; if (complete) { // Or only if the transition isn't cancelled; here? [oldVC endAppearanceTransition]; [newVC endAppearanceTransition]; [oldVC removeFromParentViewController]; [newVC didMoveToParentViewController:weakSelf]; } else { [newVC removeFromParentViewController]; [oldVC didMoveToParentViewController]; } } if ([context isInteractive] && [self.transitionController conformsToProtocol:@protocol(UIViewControllerInteractiveTransitioning)]) { // Start the interactive transition. [self.transitionController startInteractiveTransition:context]; } else if ([context isAnimated] && [self.transitionController conformsToProtocol:@protocol(UIViewControllerAnimatedTransitioning)]) { // Start the animated transition. [self.transitionController animateTransition:context]; } else { // Just complete the transition. [context completeTransition:YES]; } }
所以如果我打电话endAppearanceTransition
不pipe过渡是否被取消,那么当过渡被取消时,我的callback看起来像这样:
oldVC viewWillDisappear: // Fine newVC viewWillAppear: // Fine // ... some time later transition is cancelled ... oldVC viewDidDisappear: // Wrong! This view controller's view is staying. newVC viewDidAppear: // Wrong! The appearance of this view controllers view was cancelled.
如果我只是在转换成功的时候才调用endAppearanceTransition
,事情看起来会好一些:
oldVC viewWillDisappear: // Fine newVC viewWillAppear: // Fine // ... some time later transition is cancelled ... // ... silence. (which is correct - neither view actually appeared or disappeared, // and I can undo side effects in viewWill(Dis)Appear using the // transitionCoordinator object)
但是,接下来我开始转换,我没有收到任何视图callback 。 下一组视图外观callback仅在beginAppearanceTransition:animated:
后面是endApperanceTransition
调用。 值得注意的是,我没有收到经常报告的“ 不平衡调用ViewController的开始/结束外观转换 ”控制台警告。
在WWDC 2013 Session 218(使用视图控制器的自定义转换) Bruce Nilo对他的同事们讲了一个相当有趣的笑话,告诉他这个viewWillAppear:
& viewWillDisappear:
真的应该叫做viewMightAppear:
& viewMightDisappear:
参见42节开始的部分:00) 。 鉴于我们现在处于可取消互动手势领域,似乎我们需要一个cancelAppearanceTransition
(或者endAppearanceTransition:(BOOL)finished
)方法来定制容器。
有没有人知道我在做什么错了? 或者是可以取消的,自定义容器中的自定义转换只是不被正确支持?
所以,通常情况下,你会花费数小时的时间来解决问题,然后在问题发布后,你会find答案。
我查看了UINavigationController
内置的interactivePopGestureRecognizer
的外观callbackUINavigationController
,看看在取消转换时会发生什么,看起来我应该调用什么外观方法的假设是错误的。
对于成功转换到的视图控制器,会收到以下callback(如您所期望的):
transition finished viewWillAppear: ----------> viewDidAppear:
但是对于不成功的视图控制器转换(即转换被取消),视图控制器会收到以下callback:
transition immediately cancelled followed by viewWillAppear: ----------> viewWillDisappear: ----------> viewDidDisappear:
所以我们可以看到更多的那些狡猾的视图外观语义。 我不确定一个视图如果还没有出现,它是如何消失的 ! 噢,我想这比稍微说出来的观点要好一点,然后什么也没有发生。
所以,返回到我的CustomTransitionContext
的transitionCompletionHandler
代码,解决scheme如下所示:
... // Register a completion handler to run when the context's completeTransition: method is called. __weak CustomContainerController *weakSelf = self; __weak CustomTransitionContext *weakContext = context; context.transitionCompletionHandler = ^(BOOL complete) { if (complete) { // End the appearance transitions we began earlier. [oldVC endAppearanceTransition]; [newVC endAppearanceTransition]; [oldVC removeFromParentViewController]; [newVC didMoveToParentViewController:weakSelf]; } else { // Before ending each appearance transition, begin an // appearance transition in the opposite direction. [newVC beginAppearanceTransition:NO animated:[weakContext isAnimated]]; [newVC endAppearanceTransition]; [oldVC beginAppearanceTransition:YES animated:[weakContext isAnimated]]; [oldVC endAppearanceTransition]; [newVC removeFromParentViewController]; [oldVC didMoveToParentViewController]; } } ....
所以现在外观callback看起来像这样:
oldVC viewWillDisappear: newVC viewWillAppear: // ... some time later transition is cancelled ... newVC viewWillDisappear: newVC viewDidDisappear: oldVC viewWillAppear: oldVC viewDidAppear:
另外,随后的转换现在会按预期触发callback(因为我们正在closures所有转换,并调用endAppearanceTransition
)。