iOS中的转场研究(3)

实现自定义的Container View Controller

上一篇文章中提到了如何定制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实例。动画

使用Modal presentation时须要注意的状况

当咱们使用UIViewControllerpresentViewController: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

使用AddChildViewController API实现本身的Container View Controller

咱们偶尔会但愿有一个跟Navigation View Controller差很少的容器,可是又不但愿像Navigation View Controller那么笨重,且限制多多。咱们知道Navigation View Controller在Interface Builder中,其Navigation Bar能容纳的元素样式并不丰富,尽管大多数时候,咱们可以经过UIAppearance来定制一些样式,但咱们但愿定制能容纳更加丰富的元素的Navigation Bar,或者其余定制的导航界面的时候,但愿可以实现一个相似的容器。咱们固然能够模仿Navigation View Controller的公开API实现一个差很少的东西,若是咱们要很方便的使用自定义Segue和任意跳转的Unwind Segue的话,还须要以特定的方式实现上面提到的一些方法。UIViewControlleraddChildViewController:方法一样能够作出相似的功能,并且相比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:方法来调整子视图的层次。


  1. 下面的代码修改自iOS6 by Tutorial中的示例代码。 

相关文章
相关标签/搜索