转载自:https://blog.csdn.net/u013749108/article/details/54234225
两个Controller之间的交互,相比正常的Push和Present,转场动画是iOS里比较酷炫的一种效果,能够以各种效果平滑的切换两个不同的视图控制器。适当的运用转场动画,会让你的APP变得更加生动有趣。
这篇文章会以Present为例,讲述如何自定义一个转场动画。后续会有Push,以及更复杂的转场介绍。
准备:首先要两个UIViewController和一个继承与UIPercentDrivenInteractiveTransition的类。
ViewController1
ViewController2
PresentTransitionAnimator
场景:ViewController1 从右边present 到 ViewController2,并支持手势拖动返回。
PresentTransitionAnimator.h
#import
@interface PresentTransitionAnimator : UIPercentDrivenInteractiveTransition
@property (nonatomic, assign) BOOL isInteractive;//是否在拖动
@property (nonatomic, assign) BOOL isDismiss;//是present还是dismiss
@property (nonatomic, assign) float panRatio;//拖动比率
- (id)initWithModalViewController:(UIViewController *)modalViewController;//初始化
@end
PresentTransitionAnimator.m
@property (nonatomic, weak) UIViewController *modalController;//目标Controller
@property (nonatomic, strong) id transitionContext;//转场上下文,用来获取这两个交互的UIViewController。
@property (nonatomic, strong) UIPanGestureRecognizer *gesture;//拖动手势
- (instancetype)initWithModalViewController:(UIViewController *)modalViewController{
self = [super init];
if (self) {
_modalController = modalViewController;
//创建手势
self.gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
self.gesture.delegate = self;
[self.modalController.view addGestureRecognizer:self.gesture];
}
return self;
}
转场动画的协议
#pragma mark - UIViewControllerTransitioningDelegate Methods
//Present转场的开始
- (id)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
self.isDismiss = NO;
return self;
}
//Dismiss转场的开始
- (id)animationControllerForDismissedController:(UIViewController *)dismissed{
self.isDismiss = YES;
return self;
}
//手势Present转场的开始(由于这个demo不支持手势拖动Present,所以返回nil)
- (id)interactionControllerForPresentation:(id)animator{
return nil;
}
//手势Dismiss转场的开始,同理,假如不支持手势Dismiss,则返回nil。
- (id)interactionControllerForDismissal:(id)animator{
// Return nil if we are not interactive
if ( self.isInteractive) {
self.isDismiss = YES;
return self;
}
return nil;
}
转场动画需要的时间
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id)transitionContext{
return 0.25;
}
转场核心代码,我们转场动画的细节处理,适用于点击按钮的自动跳转或者返回。手势拖动不会调用该方法,需要我们单独处理。
- (void)animateTransition:(id)transitionContext{
-
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];//获取容器View
//*这里需要注意!如果是present,fromViewController为ViewCOntroller1,toViewController为ViewController2.
//若是dismiss,则正好相反。
if (!self.isDismiss) {//Present
//*把目标的view放到容器View中
[containerView addSubview:toViewController.view];
toViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
//*转场开始前,toViewController的View位于屏幕的右侧看不见的地方
CGRect startRect = CGRectMake(CGRectGetWidth(containerView.bounds), 0, CGRectGetWidth(containerView.bounds), CGRectGetHeight(containerView.bounds));
toViewController.view.frame = startRect;
[fromViewController beginAppearanceTransition:NO animated:YES];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.alpha = self.behindViewAlpha;
//*动画的执行过程是从右侧看不见的地方,往左移动到到屏幕的位置
toViewController.view.frame = CGRectMake( 0, 0,
CGRectGetWidth(toViewController.view.frame),
CGRectGetHeight(toViewController.view.frame) );
} completion:^(BOOL finished) {
//*转场结束,把操作权给系统。
[fromViewController endAppearanceTransition];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
//*dismiss
else{
[containerView bringSubviewToFront:fromViewController.view];
toViewController.view.alpha = self.behindViewAlpha;
CGRect endRect = CGRectMake(CGRectGetWidth(fromViewController.view.frame), 0, CGRectGetWidth(fromViewController.view.frame), CGRectGetHeight(fromViewController.view.frame));
[toViewController beginAppearanceTransition:YES animated:YES];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1.0f;
fromViewController.view.frame = endRect;
} completion:^(BOOL finished) {
[toViewController endAppearanceTransition];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
}
备注:可以看到转场动画的present和dismiss过程,都是在一个函数内完成的,主要区别就是处理fromViewController.view和toViewController.view的frame的方式不同罢了。
处理手势:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer{
CGPoint location = [recognizer locationInView:self.modalController.view.window];
CGPoint point = [recognizer translationInView:self.modalController.view];
//禁止左滑
if (point.x<0) {
return;
}
//*开始滑动
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.isInteractive = YES;
self.panLocationStart = location.x;
[self.modalController dismissViewControllerAnimated:YES completion:nil];
}
//*滑动中
else if (recognizer.state == UIGestureRecognizerStateChanged){
CGFloat animationRatio = 0;
animationRatio = (location.x - self.panLocationStart) / ( CGRectGetWidth([self.modalController view].bounds) );
self.panRatio = animationRatio;
[self updateInteractiveTransition:animationRatio];
}
//*滑动
else if (recognizer.state == UIGestureRecognizerStateEnded){
//*控制滑动到某程度,松手后是dismiss还是恢复到滑动前的状态。
if ( self.panRatio > 0.12 ) {
[self finishInteractiveTransition];
}else{
[self cancelInteractiveTransition];
}
self.isInteractive = NO;
}
}
随着拖动的位置更新view的frame以及透明度
- (void)updateInteractiveTransition:(CGFloat)percentComplete{
if (!self.bounces && percentComplete < 0) {
percentComplete = 0;
}
id transitionContext = self.transitionContext;
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
toViewController.view.alpha = self.behindViewAlpha + ( (1 - self.behindViewAlpha) * ABS(percentComplete) );
CGRect updateRect = CGRectMake((CGRectGetWidth(fromViewController.view.bounds) * percentComplete),
0,
CGRectGetWidth(fromViewController.view.frame),
CGRectGetHeight(fromViewController.view.frame));
if ( isnan(updateRect.origin.x) || isinf(updateRect.origin.x) ) {
updateRect.origin.x = 0;
}
if ( isnan(updateRect.origin.y) || isinf(updateRect.origin.y) ) {
updateRect.origin.y = 0;
}
fromViewController.view.frame = updateRect;
}
完成拖动返回
- (void)finishInteractiveTransition{
id transitionContext = self.transitionContext;
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect endRect;
endRect = CGRectMake( CGRectGetWidth(fromViewController.view.bounds),
0,
CGRectGetWidth(fromViewController.view.frame),
CGRectGetHeight(fromViewController.view.frame) );
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1.0f;
fromViewController.view.frame = endRect;
}completion:^(BOOL finished) {
[toViewController endAppearanceTransition];
[transitionContext completeTransition:YES];
}];
}
取消拖动
- (void)cancelInteractiveTransition{
id transitionContext = self.transitionContext;
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[toViewController beginAppearanceTransition:NO animated:YES];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = self.behindViewAlpha;
fromViewController.view.frame = CGRectMake( 0, 0,
CGRectGetWidth(fromViewController.view.frame),
CGRectGetHeight(fromViewController.view.frame) );
} completion:^(BOOL finished) {
[toViewController endAppearanceTransition];
[transitionContext completeTransition:NO];
}];
}
完成手势拖动的协议 UIViewControllerInteractiveTransitioning
#pragma mark - UIViewControllerInteractiveTransitioning
- (void)startInteractiveTransition:(id)transitionContext{
self.transitionContext = transitionContext;
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[toViewController beginAppearanceTransition:YES animated:YES];
toViewController.view.alpha = self.behindViewAlpha;
UIView *containerView = [transitionContext containerView];
[containerView bringSubviewToFront:fromViewController.view];
}
完成转场
#pragma mark - 完成转场
- (void)animationEnded:(BOOL)transitionCompleted{
self.isInteractive = NO;
self.transitionContext = nil;
}
如何使用:
ViewController1.m
ViewController2 *vc = [[ViewController2 alloc] init];
[self presentViewController:vc animated:YES completion:nil];
ViewController2.m
- (instancetype)init
{
self = [super init];
if ( self ) {
_animator = [[PXPresentTransitionAnimator alloc] initWithModalViewController:self];
self.transitioningDelegate = self.animator;
self.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
注:Present转场和Push转场大致上是差不多的,至少在原理上基本相似。但还是有差异的,如果处理不好这些差异,会导致黑屏,甚至崩溃的问题。
Present转场是从fromView转换到toView,根视图 fromView 也参与了转场。在转场结束后,fromView 可能依然可见。
而Push转场其根视图并未参与转场,转场结束后fromView 会被主动移出视图结构。
下一节会针对Push的转场动画进行代码诠释,这样对比着会更加清晰。
版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/u013749108/article/details/54234225