前言:在之前的俩篇中,很多人可能发现我们一直在用模态的方式转换控制器,但是在开发过程中大多数情况下是用导航控制器来管理自己的视图控制器的。那么在Push或Pop的时候怎么实现过渡效果呢?其实很简单,和之前的基本一样,只是返回事件动画对象和手势交互动画对象的代理方法不一样了(此时是UINavigationControllerDelegate中的方法)仅此而已。那么,举个例子来练习一下吧。
正文:
Push时的效果:
Pop时的效果:
1. 创建俩个视图控制器,FirstCollectionViewController和SecondViewController
在这里只贴出一些关键性的代码,以免影响阅读,完整代码在文章最后有下载。
FirstCollectionViewController
@interface FirstCollectionViewController : UICollectionViewController
@property (nonatomic, strong) NSIndexPath *seletedIndexPath;
@end
@interface FirstCollectionViewController () <UINavigationControllerDelegate>
@end
@implementation FirstCollectionViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.navigationController.delegate = self;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
SecondViewController *secondVc = [[SecondViewController alloc] init];
[self.navigationController pushViewController:secondVc animated:YES];
}
#pragma mark - UINavigationControllerDelegate
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPush) {
FlatZoomPushTransition *pushTransition = [[FlatZoomPushTransition alloc] init];
return pushTransition;
}
return nil;
}
(1) 可以看出只要在NavigationController的代理方法中,返回事件动画对象就ok了。和模态方式比较只是返回的方法不同而已。
SecondViewController
@interface SecondViewController : UIViewController
@property (nonatomic, assign) UIImageView *imageInSecond;
@end
@interface SecondViewController () <UINavigationControllerDelegate>
@property (nonatomic, strong) FlatZoomInteractivePopTransition *popInteractiveTransition;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.popInteractiveTransition = [[FlatZoomInteractivePopTransition alloc] initWithController:self];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.navigationController.delegate = self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPop) {
FlatZoomPopTransition *popTransition = [[FlatZoomPopTransition alloc] init];
return popTransition;
}
return nil;
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
if ([animationController isKindOfClass:[FlatZoomPopTransition class]]) {
return self.popInteractiveTransition.interactiving ? self.popInteractiveTransition : nil;
}
return nil;
}
(1)注意俩个视图控制器中代理对象设置的位置都是在viewWillAppear中。这是为什么呢?
首先,当SecondViewController显示出来后,当前NavigationController的代理对象变为当前控制器,所以在FirstCollectionViewController重新出现时,需要将代理对象变为FirstCollectionViewController,所以viewWillAppear是比较合适的位置。
其次,当在当SecondViewController中用手势移除控制器的过程中,有可能会取消手势,此时会比较尴尬,因为FirstCollectionViewController的View已经出现了,所以navigationController的代理又被设置成了FirstCollectionViewController对象,所以需要在viewWillAppear中将代理对象重新设置回SecondViewController。
2.分析动画实现的策略:
Push的动画过程
在点击cell之后,将点击的cell截图
—>截图放在containerView上覆盖住cell上的图片(坐标系转换),并隐藏cell上的图片
—> 获取详情控制器中大图,算出该图在containerView中的坐标(坐标系转换),并隐藏该图
—> 图层关系:FirstCollectionViewController的View(最下面),SecondViewController的View(中间),截图(最上面)
—> 对SecondViewController的view的alpha和截图的frame做动画
—> 动画结束后,将截图移除,大图和cell上的图片都显示出来即可。
Pop的过程基本上是Push的逆向:
在过渡开始时,将SecondViewController中的大图截图放在containerView上覆盖住原图片(坐标系转换),隐藏大图。
—> 获取FirstCollectionViewController中之前被选中的cell,算出该cell中图片在containerView中的坐标(坐标系转换),隐藏该图片。
—> 图层关系:FirstCollectionViewController的View(最下面),SecondViewController的View(中间),截图(最上面)
—> 对SecondViewController的view的alpha和截图的frame做动画
—> 动画结束后,将截图移除,cell上的图片显示出来即可。
坐标转换可用下面方法完成:
- (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view;
Push的事件动画对象:FlatZoomPushTransition
@implementation FlatZoomPushTransition
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.8f;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
// 1. 获取源和目标控制器
FirstCollectionViewController *fromVc = (FirstCollectionViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
SecondViewController *toVc = (SecondViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];
// 2. 将cell中的imageView截图,并对截图设置frame
// 1) 找到选中的cell
NSIndexPath *seletedIndexPath = [[fromVc.collectionView indexPathsForSelectedItems] firstObject];
// pop时会用到
fromVc.seletedIndexPath = seletedIndexPath;
FirstCellCollectionViewCell *cell = (FirstCellCollectionViewCell *)[fromVc.collectionView cellForItemAtIndexPath:seletedIndexPath];
// 2) 截图
UIView *snapShotView = [cell.imageInCell snapshotViewAfterScreenUpdates:NO];
cell.imageInCell.hidden = YES;
// 3) 转换坐标系
CGRect snapShotFrame = [containerView convertRect:cell.imageInCell.frame fromView:cell];
snapShotView.frame = snapShotFrame;
// 3. 设置目标控制器的view的初始状态
toVc.view.frame = [transitionContext finalFrameForViewController:toVc];
toVc.view.alpha = 0.0f;
toVc.imageInSecond.hidden = YES;
// 注意顺序
[containerView addSubview:toVc.view];
[containerView addSubview:snapShotView];
// 4. 动画
NSTimeInterval duration = [self transitionDuration:transitionContext];
CGRect snapShotFinialFrame = [containerView convertRect:toVc.imageInSecond.frame fromView:toVc.view];
[UIView animateWithDuration:duration
delay:0
usingSpringWithDamping:0.5f
initialSpringVelocity:0.5f
options:UIViewAnimationOptionCurveEaseIn
animations:^{
snapShotView.frame = snapShotFinialFrame;
toVc.view.alpha = 1.0f;
} completion:^(BOOL finished) {
cell.imageInCell.hidden = NO;
toVc.imageInSecond.hidden = NO;
[snapShotView removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
Pop的事件动画对象:FlatZoomPopTransition
@implementation FlatZoomPopTransition
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.8f;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
// 1. 获取源和目标控制器
SecondViewController *fromVc = (SecondViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
FirstCollectionViewController *toVc = (FirstCollectionViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];
// 2. 截图,并设置截图在containerView中的frame
UIView *snapShotView = [fromVc.imageInSecond snapshotViewAfterScreenUpdates:NO];
fromVc.imageInSecond.hidden = YES;
CGRect currentFrame = [containerView convertRect:fromVc.imageInSecond.frame fromView:fromVc.view];
snapShotView.frame = currentFrame;
// 3. 设置目标控制器的初始状态
toVc.view.frame = [transitionContext finalFrameForViewController:toVc];
// 1). 获取cell隐藏图片
FirstCellCollectionViewCell *cell =(FirstCellCollectionViewCell *)[toVc.collectionView cellForItemAtIndexPath:toVc.seletedIndexPath];
cell.imageInCell.hidden = YES;
// 注意放置顺序
[containerView insertSubview:toVc.view belowSubview:fromVc.view];
[containerView addSubview:snapShotView];
// 4. 动画
CGRect finialFrame = [containerView convertRect:cell.imageInCell.frame fromView:cell];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
delay:0
usingSpringWithDamping:0.5
initialSpringVelocity:0.8
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
fromVc.view.alpha = 0.0f;
snapShotView.frame = finialFrame;
}
completion:^(BOOL finished) {
cell.imageInCell.hidden = NO;
fromVc.imageInSecond.hidden = NO;
[snapShotView removeFromSuperview];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
Pop的手势交互动画对象:FlatZoomInteractivePopTransition
@interface FlatZoomInteractivePopTransition : UIPercentDrivenInteractiveTransition
- (instancetype)initWithController:(UIViewController *)vc;
@property (nonatomic, assign) BOOL interactiving;
@end
@interface FlatZoomInteractivePopTransition ()
@property (nonatomic, strong) UIViewController *vc;
@end
@implementation FlatZoomInteractivePopTransition
- (instancetype)initWithController:(UIViewController *)vc
{
if (self = [super init]) {
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panAction:)];
[vc.view addGestureRecognizer:panGestureRecognizer];
self.vc = vc;
}
return self;
}
- (void)panAction:(UIPanGestureRecognizer *)panGestureRecognizer
{
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
// 手指横向移动距离
CGFloat translationx = [panGestureRecognizer translationInView:panGestureRecognizer.view].x;
// 手势完成度
CGFloat complatedRation = translationx / (screenWidth * 1);
// 将比例限制在0~1之间
complatedRation = MIN(1.0, MAX(0.0, complatedRation));
if (panGestureRecognizer.state == UIGestureRecognizerStateBegan)
{
self.interactiving = YES;
[self.vc.navigationController popViewControllerAnimated:YES];
}
else if (panGestureRecognizer.state == UIGestureRecognizerStateChanged)
{
// 告知系统汇报进度
[self updateInteractiveTransition:complatedRation];
}
else if (panGestureRecognizer.state == UIGestureRecognizerStateCancelled)
{
self.interactiving = NO;
// 告知系统取消过渡
[self cancelInteractiveTransition];
}
else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded)
{
self.interactiving = NO;
// 手指从屏幕抬起时,手势进行一半即可认为是完成过渡,反则取消过渡
if (complatedRation > 0.5) {
[self finishInteractiveTransition];
} else {
[self cancelInteractiveTransition];
}
}}
@end
Demo地址:FlatZoomTransition-Demo