效果图
交互式动画的实现过程
动画
1、给UINavigationController添加代理,需实现UINavigationControllerDelegate
协议。
2、创建动画类,该类需实现UIViewControllerAnimatedTransitioning
协议。
手势交互
3、给UINavigationController添加手势和完成手势百分比的判定。
4、实现UIViewControllerAnimatedTransitioning
协议的手势方法,返回UIPercentDrivenInteractiveTransition
对象。
给UINavigationController添加代理
创建PJNavigationInteractiveTransition
类用于对UINavigationController代理的实现
// PJNavigationInteractiveTransition.h
self.navTransition =
[[PJNavigationInteractiveTransition alloc] initWithViewController:self];
// PJNavigationInteractiveTransition.h
- (instancetype)initWithViewController:(UINavigationController *)vc {
self = [super init];
if (self) {
_navigationController = vc;
_navigationController.delegate = self;
}
return self;
}
实现UINavigationControllerDelegate
协议
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
if (operation == UINavigationControllerOperationPop) {
return [[PJPopAnimation alloc] init];
}
_fromViewController = fromVC;
return nil;
}
- 当调用
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
方法后系统将自动调用该方法。 - 当返回为nil时,将使用默认的转场。否者则使用特定的动画类。
实现动画类
动画类主要就是实现UINavigationControllerDelegate
协议中的两个required方法
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 0.3;
}
这个方法就是返回动画执行的时间
</br>
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
// 1
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 2
UIView *fromView;
UIView *toView;
if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
toView = [transitionContext viewForKey:UITransitionContextToViewKey];
} else {
fromView = fromViewController.view;
toView = toViewController.view;
}
// 3
fromView.frame = [transitionContext initialFrameForViewController:fromViewController];
toView.frame = [transitionContext finalFrameForViewController:toViewController];
// 4
UIView *containerView = [transitionContext containerView];
// 5
[containerView insertSubview:toView belowSubview:fromView];
// 6
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromView.transform = CGAffineTransformMakeTranslation([UIScreen mainScreen].bounds.size.width, 0);
} completion:^(BOOL finished) {
fromView.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
1、获取fromViewController与toViewController
fromViewController与toViewController是相对的,如下图所示
2、获取fromView与toView
当系统版本为iOS8.0以上时,需使用viewForKey。
3、设置frame。
4、获取containerView
containerView就是转场动画执行中屏幕所展示的View,相当于是动画展示的平台。
5、将fromView与toView加入至containerView中
6、设置动画
当动画结束后必须调用completeTransition方法以告知系统动画完成。
给UINavigationController添加手势和完成手势百分比的判定
给UINavigationController添加手势
UIGestureRecognizer *gesture = self.interactivePopGestureRecognizer;
gesture.enabled = NO;
UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] init];
popRecognizer.delegate = self;
popRecognizer.maximumNumberOfTouches = 1;
[gesture.view addGestureRecognizer:popRecognizer];
self.navTransition = [[PJNavigationInteractiveTransition alloc] initWithViewController:self];
[popRecognizer addTarget:self.navTransition action:@selector(handleControllerPop:)];
- 禁止系统的侧滑返回
- 添加我们自己的Pan手势
手势的限制
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
// Ignore when no view controller is pushed into the navigation stack.
if (self.viewControllers.count <= 1) {
return NO;
}
// Ignore when the beginning location is beyond max allowed initial distance to left edge.
CGPoint beginningLocation = [gestureRecognizer locationInView:gestureRecognizer.view];
if (maxAllowedInitialDistance > 0 && beginningLocation.x > maxAllowedInitialDistance) {
return NO;
}
// Ignore pan gesture when the navigation controller is currently in transition.
if ([[self.navigationController valueForKey:@"_isTransitioning"] boolValue]) {
return NO;
}
// Prevent calling the handler when the gesture begins in an opposite direction.
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
if (translation.x <= 0) {
return NO;
}
// When the active view controller allow interactive pop.
UIViewController *topViewController = self.viewControllers.lastObject;
if (topViewController.pj_interactivePopDisabled) {
return NO;
}
return YES;
}
手势百分比的判定
- (void)handleControllerPop:(UIGestureRecognizer *)gestureRecognizer {
/**
* 稳定进度区间,让它在0.0(未完成)~1.0(已完成)之间
*/
CGFloat progress = 0.0;
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *recognizer = (UIPanGestureRecognizer *)gestureRecognizer;
progress = [recognizer translationInView:recognizer.view].x / recognizer.view.bounds.size.width;
progress = MIN(1.0, MAX(0.0, progress));
}
[self p_handleGestureRecognizer:gestureRecognizer Progress:progress];
}
- (void)p_handleGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer Progress:(CGFloat)progress {
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
[self.navigationController popViewControllerAnimated:YES];
} else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
[self.interactivePopTransition updateInteractiveTransition:progress];
} else if (gestureRecognizer.state == UIGestureRecognizerStateEnded ||
gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
if (progress > 0.5) {
[self.interactivePopTransition finishInteractiveTransition];
if (_fromViewController && _fromViewController.pj_transitionFinishedBlock) {
_fromViewController.pj_transitionFinishedBlock();
}
} else {
[self.interactivePopTransition cancelInteractiveTransition];
}
self.interactivePopTransition = nil;
}
}
UIPercentDrivenInteractiveTransition是什么
这是一个实现了UIViewControllerInteractiveTransitioning接口的类,为我们预先实现和提供了一系列便利的方法,可以用一个百分比来控制交互式切换的过程。具体有以下几个重要方法:
-(void)updateInteractiveTransition:(CGFloat)percentComplete
更新百分比,一般通过手势识别的长度之类的来计算一个值,然后进行更新。
-(void)cancelInteractiveTransition
报告交互取消,返回切换前的状态
–(void)finishInteractiveTransition
报告交互完成,更新到切换后的状态
实现UIViewControllerAnimatedTransitioning
协议的手势方法
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
if ([animationController isKindOfClass:[PJPopAnimation class]]) {
return self.interactivePopTransition;
}
return nil;
}
参考
https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html#//apple_ref/doc/uid/TP40007457-CH16-SW1
https://developer.apple.com/library/content/samplecode/CustomTransitions/Introduction/Intro.html#//apple_ref/doc/uid/TP40015158-Intro-DontLinkElementID_2
https://onevcat.com/2013/10/vc-transition-in-ios7/
https://github.com/forkingdog/FDFullscreenPopGesture