一、实现自定义过渡
过渡是由使用了 UIViewControllerAnimatedTransitioning 协议的对象来实现的。我们现在新建一个继承自 NSObject 的类,取名 DSLTransitionFromFirstToSecond。将上面提到的协议加入该类,然后就可以使用他来实现我们的两个类的过渡效果了。
在这个对象中,有两个方法需要实现:animateTransition: 和 transitionDuration:。后者相当直观,就是这个过渡的持续时间,我们只要简单返回一个 NSTimeInterval 值就行。
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.3;
}
animateTransition: 方法是定义两个 ViewController 之间过渡效果的地方。这个方法会传递给我们一个参数,该参数可以让我们访问一些实现过渡所必须的对象。
- viewControllerForKey://我们可以通过他访问过渡的两个 ViewController。 - containerView://两个 ViewController 的 containerView。 - initialFrameForViewController 和 finalFrameForViewController //是过渡开始和结束时每个 ViewController 的 frame。
现在我们开始这个方法的具体实现。首先我们需要得到过渡前后两个 ViewController 以及他们的 containerView 的指针。
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext { DSLFirstViewController *fromViewController = (DSLFirstViewController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; DSLSecondViewController *toViewController = (DSLSecondViewController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];
接下来,获得我们需要过渡的 Cell,并且对它上面的 imageView 截图。这个截图就会用在我们的过渡效果中。同时,我们将这个 imageView 本身隐藏,从而让用户以为是 imageView 在移动的。
// 获得cell上imageView的截图
DSLThingCell *cell = (DSLThingCell*)[fromViewController.collectionView cellForItemAtIndexPath:[[fromViewController.collectionView indexPathsForSelectedItems] firstObject]];
//生成快照(用于我们的过渡效果中)
//设置No会立即生成快照,并不会调用重新设置颜色的方法
UIView *cellImageSnapshot = [cell.imageView snapshotViewAfterScreenUpdates:NO];
cellImageSnapshot.frame = [containerView convertRect:cell.imageView.frame fromView:cell.imageView.superview];
cell.imageView.hidden = YES;
然后,我们对第二个 viewController 进行设置,将它的放到过渡后的位置,但让他完全透明,我们会在过渡时给它一个淡入的效果。
// 初始化一开始的状态
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
toViewController.view.alpha = 0;
toViewController.imageView.hidden = YES;
[containerView addSubview:toViewController.view];
[containerView addSubview:cellImageSnapshot];
现在来做 view 的动画,移动之前生成的 imageView 的截图,淡入第二个 viewController 的 view。在动画结束后,移除 imageView 的截图,让第二个 view 完全呈现。
[UIView animateWithDuration:duration animations:^{
// 淡入第二个viewController的view
toViewController.view.alpha = 1.0;
// 将截图放到第二个viewController的imageView上
// 将rect从view中转换到当前视图中,返回在当前视图中的rect
CGRect frame = [containerView convertRect:toViewController.imageView.frame fromView:toViewController.view];
cellImageSnapshot.frame = frame;
} completion:^(BOOL finished) {
// Clean up
toViewController.imageView.hidden = NO;
cell.hidden = NO;
[cellImageSnapshot removeFromSuperview];
// 声明过渡结束
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
记住,一定别忘了在过渡结束时调用 completeTransition: 这个方法。
二、使用自定义过渡
到目前为止,我们实现了自定义过渡对象,不过我们并没有告知 UINavigationController 去使用它。接下来,将介绍我们如何做到这一点。
当一个新的 viewController 被推入或者弹出它的导航堆,它将询问它的代理,是否有一个使用了 UIViewCOntrollerAnimatedTransitioning 协议的对象,我们现在要做的,就是提供这个对象使得过渡能够展现。
首先是把 UINavigationControllerDelegate 协议加入到 DSLFirstViewController 中去。
@interface DSLFirstViewController ()<UINavigationControllerDelegate>
我们还需要给 navigationController 的 delegate 赋值。一个比较理想的地方是在 viewDidAppear:。
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 让我自己变成navigationController的delegate
self.navigationController.delegate = self;
}
别忘了在 view 消失时,把 navigationController 的 delegate 去除。
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// 我不再是 navigationController 的代理啦
if (self.navigationController.delegate == self) {
self.navigationController.delegate = nil;
}
}
现在我们可以开始实现这个长长名字的 UINavigationControllerDelegate 的方法。
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
// 检查一下是不是过渡到DSLSecondViewController
if (fromVC == self && [toVC isKindOfClass:[DSLSecondViewController class]]) {
return [[DSLTransitionFromFirstToSecond alloc] init];
}
else {
return nil;
}
}
That’s it. 当第二个 viewController 被推入进来时,navigationController 将使用我们自定义的过渡。
要实现弹回时的过渡效果,还是一样的方法,实现一个新的 DSLTransitionFromSecondToFirst 类用来过渡即可。
原文:
http://www.cocoachina.com/industry/20140623/8918.html
项目发布在GitHub中