cocoa_chen

iOS7自定义UIViewController切换效果

2015-04-03

iOS7自定义ViewController动画切换

主要知识点,如图所示:

iOS7之后的API添加了很多新功能,其中一个很有趣的功能就是自定义UIViewController的过渡效果。它降低了耦合性,更好的分离功能代码,同时也可以很方便的做出令人眼前一亮的效果。

要实现自定义的过渡效果,需要设置UIViewController的transitioningDelegate,它是一个遵守了UIViewControllerTransitioningDelegate协议的对象。首先新建个继承NSObject的对象,命名为CustomTransitionAnimation,如图所示:

修改.h代码如下:

1
2
3
4
5
6
#import <UIKit/UIKit.h>

@interface CustomTransitionAnimation : NSObject
<UIViewControllerTransitioningDelegate>

@end

同时在.m文件中添加UIViewControllerTransitioningDelegate的相关方法,这里主要是两个方法,一个是Present的动画对象,另一个是Dismiss的动画对象。我在对应的方法里添加了一个animationType标识当前的动画类型,方便在后面的逻辑中进行判断。相关方法要求返回一个遵守UIViewControllerAnimatedTransitioning协议的对象,在这里我们设置为当前的对象,并在CustomTransitionAnimation类中添加UIViewControllerAnimatedTransitioning协议的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma mark - UIViewControllerTransitioningDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source
{
self.animationType = CCAnimationTypePresent;
return self;
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
self.animationType = CCAnimationTypeDismiss;
return self;
}

接着添加UIViewControllerAnimatedTransitioning协议相关的方法,主要是两个,一个是动画的切换时间,另一个是具体的切换代码,包括动画和相关的设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
//设置Present和Dismiss的动画时间都是0.5秒
return 0.5;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
if (self.animationType == CCAnimationTypePresent) {
/*弹出动画*/
//获取containerView视图
UIView *containerView = [transitionContext containerView];
//获取新的Present视图
UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//对要Present出来的视图设置初始位置
CGRect boundsRect = [[UIScreen mainScreen] bounds];
CGRect finalFrame = [transitionContext finalFrameForViewController:toVc];
toVc.view.frame = CGRectOffset(finalFrame, 0, boundsRect.size.height);
//添加Present视图
[containerView addSubview:toVc.view];
//UIView动画切换,在这里用Spring动画做效果
NSTimeInterval interval = [self transitionDuration:transitionContext];
[UIView animateWithDuration:interval delay:0 usingSpringWithDamping:0.8 initialSpringVelocity:0.5 options:UIViewAnimationOptionCurveLinear animations:^{
toVc.view.frame = finalFrame;
}completion:^(BOOL finished) {
//通知动画已经完成
[transitionContext completeTransition:YES];
}];
}else if (self.animationType == CCAnimationTypeDismiss) {
/*消失动画*/
//获取containerView视图
UIView *containerView = [transitionContext containerView];
//获取已经在最前的Present视图
UIViewController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//获取Dismiss完将要显示的VC
UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//在Present视图下面插入视图
[containerView insertSubview:toVc.view belowSubview:fromVc.view];
//设置最终位置
CGRect boundsRect = [[UIScreen mainScreen] bounds];
CGRect originFrame = [transitionContext initialFrameForViewController:fromVc];
CGRect finalFrame = CGRectOffset(originFrame, 0, boundsRect.size.height);
//UIView动画切换
NSTimeInterval interval = [self transitionDuration:transitionContext];
[UIView animateWithDuration:interval delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
// fromVc.view.alpha = 0.f;
fromVc.view.frame = finalFrame;
}completion:^(BOOL finished) {
//通知动画是否完成
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
}

接着在点击按钮时添加present的代码,并设置ViewController的transitionDelegate,如下:

1
2
3
4
5
6
7
8
9
- (void)onButtonTapped
{
if (!_animator) {
self.animator = [[CustomTransitionAnimation alloc] init];
}
DetailViewController *vc = [[DetailViewController alloc] init];
vc.transitioningDelegate = self.animator;
[self presentViewController:vc animated:YES completion:nil];
}

运行并查看效果,将可以看到弹出的视图从底部向上运动,并在到达顶部时以弹簧的效果运动。自定义UIViewController的过渡效果基本内容就是这些,剩下的就是发挥想象,做出惊艳的切换效果吧。

下面接着说一下交互式的切换效果,为过渡添加手势功能。

如同iOS7系统自带的滑动返回一样,接着上面的内容为present视图添加向下滑动返回的效果。查看UIViewControllerTransitioningDelegate协议的内容,发现有两个以interaction开头的方法,这些正是处理交互式切换的方法。它要求返回的是遵守了UIViewControllerInteractiveTransitioning协议的对象。而iOS7中正好提供了一个UIPercentDrivenInteractiveTransition类,它实现了UIViewControllerInteractiveTransitioning的相关接口,为我们提供了一些便利的方法来控制交互式切换。

首先添加手势代码,并利用UIPanGestureRecognizer来做手势的交互

1
2
3
4
5
6
7
- (void)setPresentingVC:(UIViewController *)presentingVC
{
_presentingVC = presentingVC;
//添加手势
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[_presentingVC.view addGestureRecognizer:panGesture];
}

接着在CustomTransitionAnimation添加UIViewControllerTransitioningDelegate的交互方法(这里只处理Dismiss的交互式效果):

1
2
3
4
5
6
7
8
9
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator
{
return nil;
}

- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator
{
return self.interacting ? self.interactiveTransition : nil;
}

下面是手势的响应方法,在这里设置交互的进度等功能,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#pragma mark - UIPanGestureRecognizer Handler
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
CGPoint tranlation = [gesture translationInView:_presentingVC.view];
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
{
//设置交互标识为YES
self.interacting = YES;
//生成UIPercentDrivenInteractiveTransition对象
self.interactiveTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
//DismissViewController
[_presentingVC dismissViewControllerAnimated:YES completion:nil];
}
break;
case UIGestureRecognizerStateChanged:
{
//计算当前百分比值
CGFloat percent = tranlation.y / CGRectGetHeight(_presentingVC.view.frame);
percent = MIN(MAX(0.0, percent), 1.0);
//用updateInteractiveTransition通知更新的百分比
[self.interactiveTransition updateInteractiveTransition:percent];
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
{
//设置交互标识为NO
self.interacting = NO;
//判断是否完成交互
if (tranlation.y > 200) {
[self.interactiveTransition finishInteractiveTransition];
}else{
[self.interactiveTransition cancelInteractiveTransition];
}
//置空UIPercentDrivenInteractiveTransition对象
self.interactiveTransition = nil;
}
break;
default:
break;
}
}

最后一步,在点击按钮的时候设置添加手势的ViewController,修改之前的点击代码

1
2
3
4
5
6
7
8
9
10
11
- (void)onButtonTapped
{
if (!_animator) {
self.animator = [[CustomTransitionAnimation alloc] init];
}
DetailViewController *vc = [[DetailViewController alloc] init];
vc.transitioningDelegate = self.animator;
//设置可交互的ViewController,将为该ViewController添加手势交互
[self.animator setPresentingVC:vc];
[self presentViewController:vc animated:YES completion:nil];
}

运行项目并且上下滑动Present视图查看效果,也可以从Github下载本文章的完整Demo。

进阶功能:

1.截图大法:配合- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates方法来实现更炫的过渡效果。

2.mask遮罩:配合view.layer.mask来定制遮罩效果,可参考RN的这篇文章来实现VC间的遮罩过渡。

总结:

目前应用基本都从iOS7开始适配,自定义VC切换将会是UI交互的基本也是很重要的一部分,熟悉并掌握也会更容易做出令人惊艳的交互设计。学习之~

Tags: iOS
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章