前言:今天会在上一篇的例子的基础上扩充手势交互过渡。手势交互过渡和事件过渡的实现方法差别不大,只需在代理方法中返回一个手势交互过渡动画对象,主要差别就是这个动画对象的实现上,这个动画对象遵守的是UIViewControllerInteractiveTransitioning协议,而不再是 UIViewControllerAnimatedTransitioning。原因在于事件过渡的过程是时间的函数,我们只要告诉系统动画时间,系统就会根据这个时间等分过程将画面渲染。但是手势过渡的过程是手势完成度的函数,那么我们就需要向系统汇报这个手势完成度,如何汇报等会再说。
正文:最终效果如下
1. 实现transitionDelegate的代理方法
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;
它返回呈现试图控制器时的动画对象,在[self presentViewController:redVC animated:YEScompletion:nil];之后被调用
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
{
return self.dimissInteractiveTranstion.interacting ? self.dimissInteractiveTranstion : nil;
}
由于我只需要在解散redViewController时需要过渡动画,所以只需要实现这一个方法。
2. 手势交互动画对象实现
前言中提到手势交互动画对象要向系统汇报手势完成百分比。SDK中提供了UIPercentDrivenInteractiveTransition这个类,它是遵守UIViewControllerInteractiveTransitioning协议帮助我们完成相应的工作。这个类提供了这三个方法:
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
- (void)cancelInteractiveTransition;
- (void)finishInteractiveTransition;
我们的手势交互动画对象只需继承这个类,并且在手势变化时调用第一个方法,在手势取消或未完成时调用第二个方法,在手势完成时调用第三个方法就可以了。
@interface ViewController ()<UIViewControllerTransitioningDelegate, UINavigationControllerDelegate>
@property (nonatomic, strong) PresentAnimater *presentAnimater;
@property (nonatomic, strong) DismissAnimater *dismissAnimater;
@property (nonatomic, strong) DismissInteractiveTranstion *dimissInteractiveTranstion;
@end
@implementation ViewController
- (void)buttonClicked:(UIButton *)btn
{
RedViewController *redVC = [[RedViewController alloc] init];
redVC.transitioningDelegate = self;
self.dimissInteractiveTranstion = [[DismissInteractiveTranstion alloc] initWithController:redVC];
[self presentViewController:redVC animated:YES completion:nil];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return self.presentAnimater;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return self.dismissAnimater;
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
{
return self.dimissInteractiveTranstion.interacting ? self.dimissInteractiveTranstion : nil;
}
@implementation DismissAnimater
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.4;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *scVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect scFrame = scVc.view.frame;
CGRect toFrame = scFrame;
toFrame.origin.x = scFrame.size.width;
toVc.view.frame = CGRectInset(scFrame, 20, 20);
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toVc.view];
[containerView sendSubviewToBack:toVc.view];
CGFloat duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration animations:^{
scVc.view.frame = toFrame;
toVc.view.frame = scFrame;
toVc.view.alpha = 1;
} completion:^(BOOL finished) {
// NO : 会还原动画前所有View的位置。
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
@interface DismissInteractiveTranstion : UIPercentDrivenInteractiveTransition
- (instancetype)initWithController:(UIViewController *)vc;
@property (nonatomic, assign) BOOL interacting;// 用于判定是否需要手势过渡。
@end
@interface DismissInteractiveTranstion ()
@property (nonatomic, strong) UIViewController *scVc;
@property (nonatomic, assign) BOOL shouldComplated;
@end
@implementation DismissInteractiveTranstion
- (instancetype)initWithController:(UIViewController *)vc
{
if (self = [super init]) {
self.scVc = vc;
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGestureRecognizer:)];
[vc.view addGestureRecognizer:panGR];
}
return self;
}
- (void)handleGestureRecognizer:(UIPanGestureRecognizer *)gr
{
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGFloat translate = [gr translationInView:gr.view].x;
CGFloat progress = translate / width;
switch (gr.state) {
case UIGestureRecognizerStateBegan:
{
self.interacting = YES;
[self.scVc dismissViewControllerAnimated:YES completion:nil];
}
break;
case UIGestureRecognizerStateChanged:
{
progress = fminf(fmax(progress, 0.0), 1.0);
self.shouldComplated = (progress > 0.5);
NSLog(@"%f", progress);
[self updateInteractiveTransition:progress];
}
break;
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded:
{
self.interacting = NO;
if (!self.shouldComplated || gr.state == UIGestureRecognizerStateCancelled) {
[self cancelInteractiveTransition];
} else {
[self finishInteractiveTransition];
}
}
break;
default:
break;
}
}
@end
(1)在这里我们说的是手势交互动画对象,为什么我还要列出事件动画对象呢?这是因为如果交互动画对象没有自己的动画(即没有实现 - ( void )startInteractiveTransition:( id < UIViewControllerContextTransitioning >)transitionContext这个代理方法),系统会自动用事件动画来替代,我们提供的手势完成百分比用来控制该动画的进度。如果交互动画对象实现了自己代理方法,就不会去调用事件动画的动画方法了。(之后几篇中会有手势过渡和事件过渡动画不同的列子)
(2)手势开始后,方法调用过程是这样的:
首先,是手势方法(handleGestureRecognizer:)-- 当dismissViewControllerAnimated:之后 --> 返回事件动画对象 --> 返回交互动画对象
(3)这里还需要注意一点,如果我们只要事件过渡,那么是不能返回手势交互过渡动画对象的,如果返回程序会卡住。那么如果我们既需要事件过渡又需要手势过渡怎么办呢?这就是交互动画对象interacting属性的作用。如果是手势过渡,在手势事件中interacting被置为YES,代理方法中返回交互动画对象。如果是事件过渡,代理方法返回nil。
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
{
return self.dimissInteractiveTranstion.interacting ? self.dimissInteractiveTranstion : nil;
}
(4)注意动画结束后,不能直接写[transitionContextcompleteTransition:YES];,因为考虑手势有取消的情况,所以要这样写[transitionContextcompleteTransition:![transitionContext transitionWasCancelled]];
Demo地址点击打开链接