iOS 自定义转场入门
前言
hi,勇敢的小伙伴儿们大家好,写ARKit的第二篇文章偶然了解了一个叫自定义转场的东西,嗯,我之前一直不知道,像我这种技术渣只会用基本的push和pop等,自定义转场这么高级的词汇我真的是第一次听说,既然学艺不精,就查漏补缺,赶紧了解,于是我搜索了相关博客,其中最通俗易懂的就是来自喵神的博客:ViewController切换 2013年的文章,年代有点久远,但不影响学习。
不正经的插一句话:我已经被喵神的匠心精神虏获,嘻嘻(逃。
我自己根据博客将大部分解释写成了注释,有兴趣可以到我的github:自定义转场入门Demo下载查看,有问题烦请指出,多谢!
正文是我根据喵神博客自己重新排版组织的,可能没有喵神写的详细,但是我自认为有一个比较系统的整理,大家可以看我的然后再看喵神的会对知识理解更加透彻。大部分解释在代码部分的注释,希望多多在意哦~
正文(2018.2.26补充)
整体上,我们将转场动画分为两个部分学习,一部分是动画切换,一部分是交互切换。
首先我们学习如何自定义ViewController的动画切换。
自定义ViewController动画切换
知识储备
在学习之前,我们先了解几个相关的协议。
@protocol UIViewControllerContextTransitioning
这个接口是用来提供切换上下文给开发者使用,Context是开发者需要关注的区别词汇。
这个接口包含了FromVC和ToVC等各类信息,一般不需要开发者自己实现。
其中比较重要的方法有:
- -(UIView *)containerView; VC切换所发生的view容器,开发者应该将切出的view移除,将切入的view加入到该view容器中。
- -(UIViewController *)viewControllerForKey:(NSString *)key; 提供一个key,返回对应的VC。现在的SDK中key的选择只有UITransitionContextFromViewControllerKey和UITransitionContextToViewControllerKey两种,分别表示将要切出和切入的VC。
- -(CGRect)initialFrameForViewController:(UIViewController *)vc; 某个VC的初始位置,可以用来做动画的计算。
- -(CGRect)finalFrameForViewController:(UIViewController *)vc; 与上面的方法对应,得到切换结束时某个VC应在的frame。
- -(void)completeTransition:(BOOL)didComplete; 向这个context报告切换已经完成。
@protocol UIViewControllerAnimatedTransitioning
这个接口负责切换的具体内容,在这个地方我们可以自定义一个基于NSObject的类,遵守UIViewControllerAnimatedTransitioning协议,实现它的代理方法,设定动画切换的时间,以及动画内容。
其中重要的方法有:
-(NSTimeInterval)transitionDuration:(id < UIViewControllerContextTransitioning >)transitionContext; 系统给出一个切换上下文,我们根据上下文环境返回这个切换所需要的花费时间(一般就返回动画的时间就好了,SDK会用这个时间来在百分比驱动的切换中进行帧的计算,后面再详细展开)。
-(void)animateTransition:(id < UIViewControllerContextTransitioning >)transitionContext; 在进行切换的时候将调用该方法,我们对于切换时的UIView的设置和动画都在这个方法中完成。
在第二个方法中,我们重写UIViewControllerContextTransitioning中的代理方法。
@protocol UIViewControllerTransitioningDelegate
这个接口的作用比较简单单一,在需要VC切换的时候系统会向遵守UIViewControllerTransitoningDelegate协议的对象询问是否使用自定义的切换效果。
其中重要的方法有:
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
-(id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id< UIViewControllerAnimatedTransitioning >)animator;
-(id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id< UIViewControllerAnimatedTransitioning >)animator;
前两个方法,中间部分是animation开头的方法,是针对动画切换的,一个是present一个是dismiss,我们分别在呈现VC和解散VC的时候,return一个遵循UIViewControllerAnimatedTransitioning协议的对象。
后两个方法,大家也可以看到中间部分是interaction开发的方法,针对的是交互式切换,如图,后面详细说明,我们先将动画切换做一个好的理解。
这样一来,我们来复习一下流程,首先我们需要创建一个被切换的ViewController,使其有拥有一个遵循UIViewControllerTransitioningDelegate的代理,如图,其中ModalViewControllerDelegate是为了执行dismiss的代理协议。
在进行动画切换的ViewController中需要将自己设定为被切换的VC的代理,如图。
并遵循同一个协议,实现其中的代理方法。
其中的present和dismiss方法需要返回一个遵循UIViewControllerAnimatedTransitioning的对象,那我们就创建一个遵循该协议的对象设定动画时间和动画内容。如图:
ok,搞定了~
是不是很容易理解? 如果觉得难理解的话,没错,我和你一样,但是总体的步骤我们清晰了,三个协议,有两个需要我们实现,一个是新建的类里实现,一个是要实现转场的ViewController里实现。
Demo Time
被自定义转场的ViewController
ModalViewController.h
#import <UIKit/UIKit.h>
@class ModalViewController;
@protocol ModalViewControllerDelegate <NSObject>
- (void) modalViewControllerDidClickedDismissButton:(ModalViewController *)viewController;
@end
@interface ModalViewController : UIViewController
@property (nonatomic,weak) id<ModalViewControllerDelegate> delegate;
@property (nonatomic,weak) id<UIViewControllerTransitioningDelegate> transitioningDelegate;
@end
ModalViewController.m
#import "ModalViewController.h"
@interface ModalViewController ()
@end
@implementation ModalViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[button setTitle:@"Dismiss me" forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)buttonClicked:(UIButton *)sender {
if (self.delegate && [self.delegate respondsToSelector:@selector(modalViewControllerDidClickedDismissButton:)]) {
[self.delegate modalViewControllerDidClickedDismissButton:self];
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
遵循UIViewControllerAnimatedTransitioning协议的类
BouncePresentAnimation.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface BouncePresentAnimation : NSObject<UIViewControllerAnimatedTransitioning>
@end
BouncePresentAnimation.m
#import "BouncePresentAnimation.h"
@implementation BouncePresentAnimation
#pragma mark - UIViewControllerAnimatedTransitioning
//系统给出一个切换上下文transitionContext,我们根据上下文环境返回这个切换多需要的花费时间,一般就返回动画的时间就好了,SDK会用这个时间来在百分比驱动的切换中进行真的计算,后面在详细展开)
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.8f;
}
//在进行切换的时候将调用该方法,我们对于切换时的UIView的设置和动画都在这个方法中完成。
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
//1.得到参与切换的两个ViewController的信息,使用context拿到它们的参照,
//ViewControllerForKey提供一个key返回对应的VC, UITransitionContextToViewControllerKey是将要切入的VC,UITransitionContextFromViewControllerKey是将要切出的VC。
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//2.对于要呈现的VC,我们希望它从屏幕下方出现,因此将初始位置设置为屏幕的下边缘
CGRect screenBounds = [[UIScreen mainScreen] bounds];
//finalFrameForViewController是某个VC的最终位置,initialFrameForViewController是某个VC的初始位置
CGRect finalFrame = [transitionContext finalFrameForViewController:toVC];
toVC.view.frame = CGRectOffset(finalFrame, 0, screenBounds.size.height);
//3.将view添加到containerView中
//此处containerView是VC切换所发生的view容器,开发者应该将切出的view移除,将切入的view加入到该containerView中
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toVC.view];
//4.开始动画。这里的动画时间长度和切换时间长度一致都是0.8s。usingSpringWithDamping的UIView动画API是iOS7新加入的,描述了一个模拟弹簧动作的动画曲线。(此处注意UIView动画在iOS7种新添加的Category:UIViewKeyframeAnimations)
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
toVC.view.frame = finalFrame;
} completion:^(BOOL finished) {
//5.在动画结束后我们必须向context报告VC切换完成,是否成功。(这里的动画切换种,没有失败的可能性,因此直接pass一个YES过去)
[transitionContext completeTransition:YES];
}];
}
@end
执行自定义转场的ViewController
MainViewController.h
#import <UIKit/UIKit.h>
@interface MainViewController : UIViewController
@end
MainViewController.m
#import "MainViewController.h"
#import "ModalViewController.h"
#import <UIKit/UIKit.h>
#import "BouncePresentAnimation.h"
#import "NormalDismissAnimation.h"
#import "SwipeUpInteractiveTransition.h"
@interface MainViewController ()<ModalViewControllerDelegate,UIViewControllerTransitioningDelegate>
@property (nonatomic,strong) BouncePresentAnimation *presentAnimation;
//@property (nonatomic,strong) NormalDismissAnimation *dismissAnimation;
//@property (nonatomic,strong) SwipeUpInteractiveTransition *transitionController;
@end
@implementation MainViewController
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_presentAnimation = [BouncePresentAnimation new];
// _dismissAnimation = [NormalDismissAnimation new];
// _transitionController = [SwipeUpInteractiveTransition new];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *buttton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buttton.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[buttton setTitle:@"Click me" forState:UIControlStateNormal];
[buttton addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttton];
}
- (void)buttonClicked:(UIButton *)sender {
ModalViewController *mvc = [[ModalViewController alloc] init];
mvc.delegate = self;
mvc.transitioningDelegate = self;
// [self.transitionController wireToViewController:mvc];
[self presentViewController:mvc animated:YES completion:nil];
}
#pragma mark - UIViewControllerTransitioningDelegate
//针对动画切换,我们需要分别在呈现VC和解散VC给出一个实现了UIViewControllerAnimatedTransitioning接口的对象(包含切换时长和如何切换)
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return self.presentAnimation;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return self.dismissAnimation;
}
//涉及交互式切换,之后再说
//- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator {
//
//}
//- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator {
// return self.transitionController.intracting ? self.transitionController : nil;
//}
#pragma mark - ModalViewControllerDelegate
- (void)modalViewControllerDidClickedDismissButton:(ModalViewController *)viewController {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
自定义ViewController交互切换
知识储备
UIPercentDrivenInteractiveTransition
这不是一个接口,这是一个类!这是一个类!这是一个遵循UIViewControllerInteractiveTransitioning协议的类!
其中重要的方法:
- -(void)updateInteractiveTransition:(CGFloat)percentComplete 更新百分比,一般通过手势识别的长度之类的来计算一个值,然后进行更新。之后的例子里会看到详细的用法
- -(void)cancelInteractiveTransition 报告交互取消,返回切换前的状态
- –(void)finishInteractiveTransition 报告交互完成,更新到切换后的状态
@protocol UIViewControllerInteractiveTransitioning
这个接口是为了实现交互式切换的功能的。大部分时候我们不需要自己来实现这个接口,所以不展开说明。
Demo Time
UIPercentDrivenInteractiveTransition的子类SwipeUpInteractiveTransition
#import <UIKit/UIKit.h>
@interface SwipeUpInteractiveTransition : UIPercentDrivenInteractiveTransition
//设定一个BOOL变量来表示是否处于切换过程中,这个布尔值将在监测到手势开始时呗设置,我们之后会在调用返回这个InteractiveTransition的时候用到
@property (nonatomic, assign) BOOL intracting;
- (void)wireToViewController:(UIViewController *)viewController;
@end
SwipeUpInteractiveTransition.m
#import "SwipeUpInteractiveTransition.h"
@interface SwipeUpInteractiveTransition ()
@property (nonatomic, assign) BOOL shouldComplete;
@property (nonatomic, strong) UIViewController *presentingVC;
@end
@implementation SwipeUpInteractiveTransition
- (void)wireToViewController:(UIViewController *)viewController {
self.presentingVC = viewController;
[self prepareGestureRecognizerInView:viewController.view];
}
- (void)prepareGestureRecognizerInView:(UIView *)view {
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
[view addGestureRecognizer:gesture];
}
- (CGFloat)completionSpeed {
return 1 - self.percentComplete;
}
- (void)handleGesture:(UIPanGestureRecognizer *)sender {
CGPoint translation = [sender translationInView:sender.view.superview];
switch (sender.state) {
case UIGestureRecognizerStateBegan:
self.intracting = YES;
[self.presentingVC dismissViewControllerAnimated:YES completion:nil];
break;
case UIGestureRecognizerStateChanged: {
//向下滑动400px或以上为100%,每次手势状态变化时根据当前手势位置计算新的百分比
CGFloat fraction = translation.y / 400.0;
fraction = fminf(fmaxf(fraction, 0.0), 1.0);
self.shouldComplete = (fraction > 0.5);
[self updateInteractiveTransition:fraction];
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
//当手势结束时,把正在切换的标设置回NO,然后进行判断。在2种我们设定了手势超过一半就认为应该结束手势,否则就应该返回原来状态,在这里使用shouldComplete判断,已决定这次transition是否应该结束。
self.intracting = NO;
if (!self.shouldComplete || sender.state == UIGestureRecognizerStateCancelled) {
[self cancelInteractiveTransition];
} else {
[self finishInteractiveTransition];
}
break;
}
default:
break;
}
}
@end
然后我们调整MainViewController的内容。
将上面MainViewController.m代码注释部分打开即可~
效果如图:
到此,入门结束了~
因为时间原因,我将未来要抽时间学习的博客贴在这里:https://www.jianshu.com/p/45434f73019e
上一篇: 申请邓白氏编码的流程
下一篇: Python输入输出与文件操作
推荐阅读
-
iOS CATransition 自定义转场动画
-
iOS 自定义转场入门
-
iOS学习之自定义弹出UIPickerView或UIDatePicker(动画效果)
-
ios 导航栏(二)——自定义导航栏
-
ROS入门(二):如何设计自定义消息
-
iOS版Cloud Firestore入门
-
JAVA 注解Annotation自定义注解入门 博客分类: java基础 JAVA 注解Annotation
-
JAVA 注解Annotation自定义注解入门 博客分类: java基础 JAVA 注解Annotation
-
iOS自定义提示弹出框实现类似UIAlertView的效果
-
Android UI设计系列之自定义SwitchButton开关实现类似IOS中UISwitch的动画效果(2)