为模态视图控制器添加交互式视图转换
参考
- GitHub 源码:shinobicontrols/iOS7-day-by-day
- 天天品尝 iOS7 甜点 :: Day 14 :: Interactive View Controller Transitions
这篇文章可能会用到之前两篇文章的知识:
- 天天品尝 iOS7 甜点 :: Day 10 :: Custom UIViewController Transitions - 导航视图控制器自定义转场
- 天天品尝 iOS7 甜点 :: Day 11 :: UIView Key-frame Animations - 关键帧动画
Flip Transition Animation - 翻转过渡效果
默认的模态视图翻转效果
自定义模态视图翻转效果
实现方式:
视图层次结构
自定义渐变动画类,遵守
UIViewControllerAnimatedTransitioning
协议以实现具体的动画效果:<!–hexoPostRenderEscape:
// ****
// SCFlipAnimation.h
#import <Foundation/Foundation.h>
/**
遵守<UIViewControllerAnimatedTransitioning>协议的自定义渐变动画
*/
@interface SCFlipAnimation : NSObject <UIViewControllerAnimatedTransitioning>
/** 确定页面的翻转方向 */
@property (nonatomic, assign) BOOL dismissal;
@end
// ****
// SCFlipAnimation.m
#import "SCFlipAnimation.h"
@implementation SCFlipAnimation
(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
// 获取各自的视图控制器
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 获取视图
UIView *containerView = [transitionContext containerView];
UIView *fromView = fromVC.view;
UIView *toView = toVC.view;
// 添加 toView 到容器视图
[containerView addSubview:toView];
// 设置 frames
CGRect initialFrame = [transitionContext initialFrameForViewController:fromVC];
fromView.frame = initialFrame;
toView.frame = initialFrame;
// 构建3D转换需要的视角
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1/CGRectGetHeight(initialFrame);
containerView.layer.sublayerTransform = transform;
// 翻转方向
CGFloat direction = self.dismissal ? -1.0 : 1.0;
// toView 反向翻转 pi/2
toView.layer.transform = CATransform3DMakeRotation(-direction * M_PI_2, 1, 0, 0);
// 关键帧动画
[UIView animateKeyframesWithDuration:[self transitionDuration:transitionContext]
delay:0.0
options:0
animations:^{
// First half is rotating in
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
// fromView 正向翻转 pi/2
fromView.layer.transform = CATransform3DMakeRotation(direction * M_PI_2, 1, 0, 0);
}];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
// toView 还原
toView.layer.transform = CATransform3DMakeRotation(0, 1, 0, 0);
}];
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 1.0;
}
@end
:hexoPostRenderEscape–>
First View Controller,遵守
UIViewControllerTransitioningDelegate
协议<!–hexoPostRenderEscape:
@interface SCViewController () <UIViewControllerTransitioningDelegate> {
SCFlipAnimation *_flipAnimation;
}
@end
@implementation SCViewController
#pragma mark - Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_flipAnimation = [SCFlipAnimation new];
}
#pragma mark -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.destinationViewController isKindOfClass:[SCModalViewController class]]) { SCModalViewController *vc = (SCModalViewController *)segue.destinationViewController;
}
}
#pragma mark - UIViewControllerTransitioningDelegate
// 弹出模态视图控制器
// 返回遵守 UIViewControllerAnimatedTransitioning 协议的实例
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
_flipAnimation.dismissal = NO;
return _flipAnimation;
}
// 消失模态视图控制器
// 返回遵守 UIViewControllerAnimatedTransitioning 协议的实例
(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
:hexoPostRenderEscape–>
{
_flipAnimation.dismissal = YES;
return _flipAnimation;
}
Model View Controller 实现按钮方法
<!–hexoPostRenderEscape:
// 使模态视图控制器消失
(IBAction)handleDismissPressed:(id)sender {
:hexoPostRenderEscape–>
[self dismissViewControllerAnimated:YES completion:NULL];
}
Interactive transitioning - 交互式转换
用手势上滑、下滑操作实现视图转换
自定义
UIPercentDrivenInteractiveTransition
子类对象,用于手势处理<!–hexoPostRenderEscape:
// ****
// SCFlipAnimationInteractor.h
#import <UIKit/UIKit.h>
#import "SCInteractiveTransitionViewControllerDelegate.h"
@interface SCFlipAnimationInteractor : UIPercentDrivenInteractiveTransition
@property (nonatomic, strong, readonly) UIPanGestureRecognizer *gestureRecogniser;
@property (nonatomic, assign, readonly) BOOL interactionInProgress;
// 实现 SCInteractiveTransitionViewControllerDelegate 自定义交互式转场协议的任意视图控制器
@property (nonatomic, weak) UIViewController<SCInteractiveTransitionViewControllerDelegate> *presentingVC;
@end
// ****
// SCFlipAnimationInteractor.m
#import "SCFlipAnimationInteractor.h"
@interface SCFlipAnimationInteractor ()
@property (nonatomic, strong, readwrite) UIPanGestureRecognizer *gestureRecogniser;
@property (nonatomic, assign, readwrite) BOOL interactionInProgress;
@end
@implementation SCFlipAnimationInteractor
- (instancetype)init
{
self = [super init];
if (self) { self.gestureRecogniser = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
}
return self;
}
#pragma mark - Gesture recognition
// 手势操作的方法
(void)handlePan:(UIPanGestureRecognizer *)pgr
{
CGPoint translation = [pgr translationInView:pgr.view];
// 手势百分比,用百分比的方式来激活指定的动画过程。
CGFloat percentage = fabs(translation.y / CGRectGetHeight(pgr.view.bounds));
switch (pgr.state) {
case UIGestureRecognizerStateBegan: {
// 手势开始,执行翻转动画
self.interactionInProgress = YES;
// 向目标对象传达执行动画的命令
[self.presentingVC proceedToNextViewController];
break;
}
case UIGestureRecognizerStateChanged: {
// 手势进行中,更新翻转进度
[self updateInteractiveTransition:percentage];
break;
}
case UIGestureRecognizerStateEnded: {
// 手势结束,分进度值处理
if(percentage < 0.5) {
// 1.进度小于0.5,取消转场动画
[self cancelInteractiveTransition];
} else {
// 2.进度大于0.5,自动完成转场动画
[self finishInteractiveTransition];
}
self.interactionInProgress = NO;
break;
}
case UIGestureRecognizerStateCancelled: {
// 取消手势
[self cancelInteractiveTransition];
self.interactionInProgress = NO;
break;
}
default:
break;
}
}
@end
:hexoPostRenderEscape–>
- 自定义交互协议
#import <Foundation/Foundation.h>
// 1.该协议的作用:
// 将手势与目标视图相关联,手势开始时,自定义的手势处理类需要向目标视图发送执行视图转场的命令。
// 2.为什么要将手势与目标视图关联呢?
// 因为手势是添加到 window 上的,这样就可以响应预期的行为。
// 3.为什么手势要添加到 window 上而不是视图上?
// 因为动画发生的时候,视图控制器中的视图将会被移除,因此手势就没有办法响应行为了。
@protocol SCInteractiveTransitionViewControllerDelegate <NSObject>
- (void)proceedToNextViewController;
@end
First View Controller
<!–hexoPostRenderEscape:
// ****
// SCViewController.h
#import <UIKit/UIKit.h>
@interface SCViewController : UIViewController
@end
// ****
// SCViewController.m
#import "SCViewController.h"
#import "SCModalViewController.h"
#import "SCFlipAnimationInteractor.h"
#import "SCFlipAnimation.h"
#import "SCInteractiveTransitionViewControllerDelegate.h"
@interface SCViewController () <SCInteractiveTransitionViewControllerDelegate, UIViewControllerTransitioningDelegate> {
SCFlipAnimationInteractor *_animationInteractor;
SCFlipAnimation *_flipAnimation;
}
@end
@implementation SCViewController
#pragma mark - Lifecycle
(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_animationInteractor = [SCFlipAnimationInteractor new];
_flipAnimation = [SCFlipAnimation new];
}
(void)viewDidAppear:(BOOL)animated
{
// 添加手势识别器到window对象中。
// 这是因为动画发生的时候,视图控制器中的视图将会被移除,因此手势就没有办法响应行为了。
// 把它添加到window上面,将会确保我们期待的行为。
if (![self.view.window.gestureRecognizers containsObject:_animationInteractor.gestureRecogniser]) {
[self.view.window addGestureRecognizer:_animationInteractor.gestureRecogniser];
}
// 在第一页中,交互的接收者指向 self 自身
_animationInteractor.presentingVC = self;
}
#pragma mark -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.destinationViewController isKindOfClass:[SCModalViewController class]]) { // Set the delegate
SCModalViewController *vc = (SCModalViewController *)segue.destinationViewController;
vc.transitioningDelegate = self;
// 模态视图的手势交互器也指向这个类的手势交互器(弱引用)
// 也就是说,这两个视图的手势交互器指向的是同一个对象
vc.interactor = _animationInteractor;
}
}
#pragma mark - SCInteractiveTransitionViewControllerDelegate
- (void)proceedToNextViewController
{
[self performSegueWithIdentifier:@"displayModal" sender:self];
}
#pragma mark - UIViewControllerTransitioningDelegate
// 弹出模态视图控制器
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
_flipAnimation.dismissal = NO;
return _flipAnimation;
}
// 消失模态视图控制器
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
_flipAnimation.dismissal = YES;
return _flipAnimation;
}
// 交互式转场方法1:展示
// 返回一个实现 UIViewControllerInteractiveTransitioning 协议的对象
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator
{
return _animationInteractor.interactionInProgress ? _animationInteractor : nil;
}
// 交互式转场方法2:消失
// 返回一个实现 UIViewControllerInteractiveTransitioning 协议的对象
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
{
return _animationInteractor.interactionInProgress ? _animationInteractor : nil;
}
@end
:hexoPostRenderEscape–>
Model View Controller
<!–hexoPostRenderEscape:
// ****
// SCModalViewController.h
#import <UIKit/UIKit.h>
#import "SCInteractiveTransitionViewControllerDelegate.h"
#import "SCFlipAnimationInteractor.h"
@interface SCModalViewController : UIViewController <SCInteractiveTransitionViewControllerDelegate>
- (IBAction)handleDismissPressed:(id)sender;
@property (nonatomic, weak) SCFlipAnimationInteractor *interactor;
@end
// ****
// SCModalViewController.m
#import "SCModalViewController.h"
@implementation SCModalViewController
#pragma mark - Lifecycle
- (void)viewDidAppear:(BOOL)animated
{
// 重新设置哪一个视图控制器是交互式转场的接收者
// 在模态页面中,交互的接收者又重新设置为模态页面自身
self.interactor.presentingVC = self;
}
#pragma mark - IBActions
// 使模态视图控制器消失
- (IBAction)handleDismissPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:NULL];
}
#pragma mark - SCInteractiveTransitionViewControllerDelegate
- (void)proceedToNextViewController
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
@end
:hexoPostRenderEscape–>
The End.