您的位置:首页 > 移动开发 > IOS开发

iOS中的转场研究(3)

2015-01-04 12:57 141 查看

实现自定义的Container View Controller

上一篇文章中提到了如何定制Segue。我们知道Unwind Segue的正常工作需要Container View Controller的支持。我们可以实现:

canPerformUnwindSegueAction:fromViewController:withSender:

viewControllerForUnwindSegueAction:fromViewController:withSender:


segueForUnwindingToViewController:fromViewController:identifier:


三个方法来定制自己的Container View Controller(以下简称“容器”)。

我们一般会在子Controller中通过实现
canPerformUnwindSegueAction:fromViewController:withSender:
来决定要不要执行相应的Unwind Segue。

在自定义的容器中,我们必须实现
viewControllerForUnwindSegueAction:fromViewController:withSender:
segueForUnwindingToViewController:fromViewController:identifier:
方法。前一个方法用来决定那个View Controller来处理Unwind Segue action,后一个方法用来返回自定义的Unwind Segue实例。

使用Modal presentation时需要注意的情况

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

Segue部分:

- (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部分:

- (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。

使用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的话,还需要以特定的方式实现上面提到的一些方法。
UIViewController
addChildViewController:
方法同样可以做出类似的功能,而且相比Modal presentation,这种方式代码更加直观。因为使用这个API实现的容器,对子Controller的管理方式与Navigation View
Controller类似。

容器的部分代码如下:

- (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代码:

- (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.parentViewC
9cf1
ontroller;

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中的示例代码。??
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: