UIViewController 的过渡
交互过程
- 通过
segue
或programmatic push/pop/modal presentation
实例化一个VC过渡 - UIKit框架询问
to-VC
的transitioningDelegate
- 若
to-VC
没有transitioningDelegate
,使用标准内置的过渡 - UIKit向
transitioningDelegate
发送消息animationControllerForPresentedController: presentingController:sourceController:
询问animation controller(即实现id<UIViewControllerAnimatedTransitioning>协议的实例对象)
。若返回nil
,使用内置过渡 - 若有返回
animation controller
,UIKit准备过渡并构建一个transitioning context(id<UIViewControllerContextTransitioning>)
- UIKit通过消息
transitionDuration:
向animation controller
询问过渡时间 - UIKit向
animation controller
发送消息animateTransition:
传入transitioning context
参数,告诉他开始执行动画 - 当动画完成,
animation controller
向transitioning context
发送completeTransition:
消息 - 当过渡完成后,UIKit保证VC和视图层次的一致性。
需要用到的API
@protocol UIViewControllerTransitioningDelegate
VC在切换的时候系统会向实现了这个接口的对象询问自定义的切换效果。
// VC过渡
-(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;
@protocol UIViewControllerAnimatedTransitioning
自定义的切换需要实现该协议,告诉VC在切换时应该怎么做。
-(NSTimeInterval)transitionDuration:(id < UIViewControllerContextTransitioning >)transitionContext; //系统给出一个切换上下文,我们根据上下文环境返回这个切换所需要的花费时间(一般就返回动画的时间就好了,SDK会用这个时间来在百分比驱动的切换中进行帧的计算,后面再详细展开)。
-(void)animateTransition:(id < UIViewControllerContextTransitioning >)transitionContext; //在进行切换的时候将调用该方法,我们对于切换时的UIView的设置和动画都在这个方法中完成。
@protocol UIViewControllerContextTransitioning
提供切换上下文给开发者使用,在切换时作为参数被传递,提供的接口有:
-(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报告切换已经完成。
实现UIViewControllerAnimatedTransitioning
协议,自定义动画控制器
//实现协议的两个方法
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.8f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
// 1. Get controllers from transition context
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 2. Set init frame for toVC
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGRect finalFrame = [transitionContext finalFrameForViewController:toVC];
toVC.view.frame = CGRectOffset(finalFrame, 0, screenBounds.size.height);
// 3. Add toVC's view to containerView
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toVC.view];
// 4. Do animate now
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. Tell context that we completed.
[transitionContext completeTransition:YES];
}];
}
交互式切换
在UIViewControllerTransitioningDelegate
中有两个方法与交互式切换相关。
-(id< UIViewControllerInteractiveTransitioning >)interactionControllerForPresentation:(id < UIViewControllerAnimatedTransitioning >)animator;
-(id< UIViewControllerInteractiveTransitioning >)interactionControllerForDismissal:(id < UIViewControllerAnimatedTransitioning >)animator;
@protocol UIViewControllerInteractiveTransitioning
提供了实现交互式切换的功能,而UIPercentDrivenInteractiveTransition
是实现该接口的类,为我们预先实现和提供了一系列便利的方法,可以用一个百分比来控制交互式切换的过程。实践过程中,我们只需继承该类,告诉这个类的实例当前的状态百分比如何,系统便根据这个百分比和我们之前设定的迁移方式为我们计算当前应该的UI渲染。其中比较重要的方法:
-(void)updateInteractiveTransition:(CGFloat)percentComplete 更新百分比,一般通过手势识别的长度之类的来计算一个值,然后进行更新。之后的例子里会看到详细的用法
-(void)cancelInteractiveTransition 报告交互取消,返回切换前的状态
–(void)finishInteractiveTransition 报告交互完成,更新到切换后的状态
具体实例:
- (void)handleGesture:(UIPanGestureRecognizer *)gestureRecognizer {
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
// 1. Mark the interacting flag. Used when supplying it in delegate.
self.interacting = YES;
[self.presentingVC dismissViewControllerAnimated:YES completion:nil];
break;
case UIGestureRecognizerStateChanged: {
// 2. Calculate the percentage of guesture
CGFloat fraction = translation.y / 400.0;
//Limit it between 0 and 1
fraction = fminf(fmaxf(fraction, 0.0), 1.0);
self.shouldComplete = (fraction > 0.5);
[self updateInteractiveTransition:fraction];//根据百分比更新交互
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
// 3. Gesture over. Check if the transition should happen or not
self.interacting = NO;
if (!self.shouldComplete || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
[self cancelInteractiveTransition];//取消交互
} else {
[self finishInteractiveTransition];//交互完成
}
break;
}
default:
break;
}
- 参考