上一篇文章中提到了如何定製Segue。咱們知道Unwind Segue的正常工做須要Container View Controller的支持。咱們能夠實現:ios
canPerformUnwindSegueAction:fromViewController:withSender:
viewControllerForUnwindSegueAction:fromViewController:withSender:
segueForUnwindingToViewController:fromViewController:identifier:
三個方法來定製本身的Container View Controller(如下簡稱「容器」)。ide
咱們通常會在子Controller中經過實現canPerformUnwindSegueAction:fromViewController:withSender:
來決定要不要執行相應的Unwind Segue。
在自定義的容器中,咱們必須實現viewControllerForUnwindSegueAction:fromViewController:withSender:
和segueForUnwindingToViewController:fromViewController:identifier:
方法。前一個方法用來決定那個View Controller來處理Unwind Segue action,後一個方法用來返回自定義的Unwind Segue實例。動畫
當咱們使用UIViewController
的presentViewController:animated:completion:
方法以Modal presentation的方式來跳轉場景的時候,狀況與在Navigation View Controller有很大不一樣。首先,使用這種方式跳轉場景的時候,跳轉到的View Controller爲Source View Controller的子Controller,而在Navigation View Controller中,全部的流程Controller基本上都是Navgation View Controller的子Controller,因此兩者在View Controller的層次管理上有不少不一樣。所以實現Modal presentation風格的Segue的時候,動畫的view不能搞錯,必須對View Controller中的頂層View操做。一個參考實現以下(略掉動畫效果代碼,僅提供轉場方法調用代碼)1:ui
Segue部分:code
- (UIView *)findTopMostViewForViewController:(UIViewController *)viewController { UIView *theView = viewController.view; UIViewController *parentViewController = viewController.parentViewController; while (parentViewController != nil) { theView = parentViewController.view; parentViewController = parentViewController.parentViewController; } return theView; } - (void)perform { UIViewController *source = self.sourceViewController; UIViewController *destination = self.destinationViewController; // Find the views that we will be animating. If the source or destination // view controller sits inside a container view controller, then the view // to animate will actually be that parent controller's view. UIView *sourceView = [self findTopMostViewForViewController:source]; UIView *destinationView = [self findTopMostViewForViewController:destination]; [source presentViewController:destination animated:NO completion:^{ // completion code here }]; }
Unwind Segue部分:orm
- (UIView *)findTopMostViewForViewController:(UIViewController *)viewController { UIView *theView = viewController.view; UIViewController *parentViewController = viewController.parentViewController; while (parentViewController != nil) { theView = parentViewController.view; parentViewController = parentViewController.parentViewController; } return theView; } - (void)perform { UIViewController *source = self.sourceViewController; UIViewController *destination = self.destinationViewController; // Find the views that we will be animating. If the source or destination // view controller sits inside a container view controller, then the view // to animate will actually be that parent controller's view. UIView *sourceView = [self findTopMostViewForViewController:source]; UIView *destinationView = [self findTopMostViewForViewController:destination]; [source dismissViewControllerAnimated:NO completion:^{ // completion code here }]; }
注意:Modal Presentation的Unwind Segue很難實現無Bug的任意跳轉,由於UIViewController
中,跟Container View Controller相關的方法的默認實現並不能很好的定位Container View Controller。而以正確的方式重寫這些方法並不容易。因此若是有任意跳轉的需求,咱們能夠嘗試本身實現一個簡單的Container View Controller。rem
咱們偶爾會但願有一個跟Navigation View Controller差很少的容器,可是又不但願像Navigation View Controller那麼笨重,且限制多多。咱們知道Navigation View Controller在Interface Builder中,其Navigation Bar能容納的元素樣式並不豐富,儘管大多數時候,咱們可以經過UIAppearance來定製一些樣式,但咱們但願定製能容納更加豐富的元素的Navigation Bar,或者其餘定製的導航界面的時候,但願可以實現一個相似的容器。咱們固然能夠模仿Navigation View Controller的公開API實現一個差很少的東西,若是咱們要很方便的使用自定義Segue和任意跳轉的Unwind Segue的話,還須要以特定的方式實現上面提到的一些方法。UIViewController
的addChildViewController:
方法一樣能夠作出相似的功能,並且相比Modal presentation,這種方式代碼更加直觀。由於使用這個API實現的容器,對子Controller的管理方式與Navigation View Controller相似。get
容器的部分代碼以下:animation
- (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender { for (UIViewController *childController in self.childViewControllers) { if ([childController canPerformUnwindSegueAction:action fromViewController:fromViewController withSender:sender]) { return childController; } } return nil; } - (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier { UIStoryboardSegue *unwindSegue = [[MyLeftToRightUnwindSegue alloc] initWithIdentifier:identifier source:fromViewController destination:toViewController]; return unwindSegue; }
Segue代碼:it
- (BOOL)controllerInStack:(UIViewController *)controller { UIViewController *fromController = self.sourceViewController; UIViewController *containerController = fromController.parentViewController; for (UIViewController *childController in containerController.childViewControllers) { if (childController == controller) { return YES; } } return NO; } - (void)perform { // A simple transition. // New scene slides in from right and old scene slides out to left. UIViewController *fromController = self.sourceViewController; UIViewController *toController = self.destinationViewController; UIViewController *parentController = fromController.parentViewController; UIView *containerView = parentController.view; [containerView addSubview:toController.view]; CGRect initialFromRect = fromController.view.frame; CGRect initialToRect = CGRectOffset(initialFromRect, initialFromRect.size.width, 0); CGRect finalFromRect = CGRectOffset(initialFromRect, -initialFromRect.size.width, 0); CGRect finalToRect = initialFromRect; toController.view.frame = initialToRect; if (![self controllerInStack:toController]) { // notify containment event. [toController willMoveToParentViewController:parentController]; } [UIView animateWithDuration:0.4f animations:^{ fromController.view.frame = finalFromRect; toController.view.frame = finalToRect; } completion:^(BOOL finished) { if (![self controllerInStack:toController]) { // Add new controller as a child controller to the container view controller [parentController addChildViewController:toController]; // notify containment event. [toController didMoveToParentViewController:toController]; } [fromController.view removeFromSuperview]; }]; }
Unwind Segue代碼:
- (void)perform { // A simple transition. // New scene slides in from left and old scene slides out to right. UIViewController *fromController = self.sourceViewController; UIViewController *toController = self.destinationViewController; UIViewController *parentController = fromController.parentViewController; UIView *containerView = parentController.view; [containerView addSubview:toController.view]; CGRect initialFromRect = fromController.view.frame; CGRect initialToRect = CGRectOffset(initialFromRect, -initialFromRect.size.width, 0); CGRect finalFromRect = CGRectOffset(initialFromRect, initialFromRect.size.width, 0); CGRect finalToRect = initialFromRect; toController.view.frame = initialToRect; [UIView animateWithDuration:0.4f animations:^{ fromController.view.frame = finalFromRect; toController.view.frame = finalToRect; } completion:^(BOOL finished) { [fromController.view removeFromSuperview]; }]; }
當咱們定義的Container View中有須要置頂的元素(好比定製的導航條)時,能夠將addSubView:
方法換成insertSubView:atIndex:
方法來調整子視圖的層次。
下面的代碼修改自iOS6 by Tutorial中的示例代碼。 ↩