ios 7 专场2

UIViewControllerAnimatedTransitioning

在UINavigationController中如何使用UIViewControllerAnimatedTransitioning? 
参考:How to use UIViewControllerAnimatedTransitioning with UINavigationController?

1.动画的FromViewController要遵循UINavigationControllerDelegate协议。 
2.FromViewController中,在如下的代理方法中返回自定义的转场动画对象:

    - (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC {
        TransitionAnimator *animator = [TransitionAnimator new];
        animator.presenting = (operation == UINavigationControllerOperationPush);
        return animator;
    }

3.创建自定义的动画类,需遵循UIViewControllerContextTransitioning协议,并实现如下的代理方法:

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
    //返回动画执行的时间
    return 0.5f;
    }

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext     {
// Grab the from and to view controllers from the context
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

// Set our ending frame. We'll modify this later if we have to
CGRect endFrame = CGRectMake(80, 280, 160, 100);

if (self.presenting) {
    fromViewController.view.userInteractionEnabled = NO;

    [transitionContext.containerView addSubview:fromViewController.view];
    [transitionContext.containerView addSubview:toViewController.view];

    CGRect startFrame = endFrame;
    startFrame.origin.x += 320;

    toViewController.view.frame = startFrame;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
        toViewController.view.frame = endFrame;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];
    }];
}
else {
    toViewController.view.userInteractionEnabled = YES;

    [transitionContext.containerView addSubview:toViewController.view];
    [transitionContext.containerView addSubview:fromViewController.view];

    endFrame.origin.x += 320;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
        fromViewController.view.frame = endFrame;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];
    }];
}
}

关于UIViewControllerAnimatedTransitioning的资料可参考:

以下内容来自 
Introduction to Custom View Controller Transitions and Animations
 
创建自定义的transition,按如下的3步:

  • Create a class that implements the UIViewControllerAnimatedTransitioning protocol. Here you will write code that performs the animation. This class is referred to as the animation controller.
  • Before presenting a view controller, set a class as its transitioning delegate. The delegate will get a callback for the animation controller to be used when presenting the view controller.
  • Implement the callback method to return an instance of the animation controller from the first step.

自定义Present过渡(Custom Present Transition)

首先创建一个animation controller。如下,创建一个CustomPresentAnimationController 类继承自NSObject

class CustomPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {

UIViewControllerAnimatedTransitioning协议有两个必须实现的方法。

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
    return 2.5
}

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
    let containerView = transitionContext.containerView()
    let bounds = UIScreen.mainScreen().bounds
    toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height)
    containerView.addSubview(toViewController.view)

    UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
        fromViewController.view.alpha = 0.5
        toViewController.view.frame = finalFrameForVC
        }, completion: {
            finished in
            transitionContext.completeTransition(true)
            fromViewController.view.alpha = 1.0
    })
}

当前的控制器要遵循UIViewControllerTransitioningDelegate协议

class ItemsTableViewController: UITableViewController, UIViewControllerTransitioningDelegate {

UIViewController有一个transitionDelegate属性,支持自定义的transitions。当一个View controller 过渡到另一个View controller的时候,系统会检查这个属性来决定是否使用自定义的transition。由UIViewControllerTransitioningDelegate来提供这个自定义的transition。

let customPresentAnimationController = CustomPresentAnimationController()

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

    if segue.identifier == "showAction" {
        let toViewController = segue.destinationViewController as UIViewController
        toViewController.transitioningDelegate = self
    }
}
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return customPresentAnimationController
}

这里写图片描述

如果想要一个不同的效果,可以把CustomPresentAnimationController.Swift中的代码:

toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height)

改为如下的形式,把View controller的frame的原点位置改在屏幕的上面:

toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, -bounds.size.height)

这里写图片描述

自定义Dismiss过渡(Custom Dismiss Transition)

UIViewControllerTransitioningDelegate 也允许指定一个animation controller来dismiss一个view controller。 
创建一个CustomDismissAnimationController类,继承自NSObject。如下:

class CustomDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning {

在这个类中添加如下的代码:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
    return 2
}

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
    let containerView = transitionContext.containerView()
    toViewController.view.frame = finalFrameForVC
    toViewController.view.alpha = 0.5
    containerView.addSubview(toViewController.view)
    containerView.sendSubviewToBack(toViewController.view)

    UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
        fromViewController.view.frame = CGRectInset(fromViewController.view.frame, fromViewController.view.frame.size.width / 2, fromViewController.view.frame.size.height / 2)
        toViewController.view.alpha = 1.0
    }, completion: {
        finished in
        transitionContext.completeTransition(true)
    })
}

在ItemsTableViewController中加入如下的属性:

let customDismissAnimationController = CustomDismissAnimationController()

并在类中添加如下的方法:

func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return customDismissAnimationController
}

UIViewControllerTransitioningDelegate 协议提供了如上的方法,这个方法用来获取dismiss view controller的animation controller。 
运行的效果如下:

这里写图片描述

动画效果并不是我们想要的。原因是改变view的frame并不会对其子view有影响。可以使用UIView的snapshotting来修改它。 
UIView的snapshotting是UIView的截屏,使其成为轻量级的UIView。我们将会在动画过程中使用这个截屏,而不是真正的view。

把animateTransition()函数替换成如下形式:

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
    let containerView = transitionContext.containerView()
    toViewController.view.frame = finalFrameForVC
    toViewController.view.alpha = 0.5
    containerView.addSubview(toViewController.view)
    containerView.sendSubviewToBack(toViewController.view)

    let snapshotView = fromViewController.view.snapshotViewAfterScreenUpdates(false)
    snapshotView.frame = fromViewController.view.frame
    containerView.addSubview(snapshotView)

    fromViewController.view.removeFromSuperview()

    UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
        snapshotView.frame = CGRectInset(fromViewController.view.frame, fromViewController.view.frame.size.width / 2, fromViewController.view.frame.size.height / 2)
        toViewController.view.alpha = 1.0
    }, completion: {
        finished in
        snapshotView.removeFromSuperview()
        transitionContext.completeTransition(true)
    })  
}

这里写图片描述

导航控制器过渡(Navigation controller transitions)

自定义CustomNavigationAnimationController类继承自NSObject,如下:

class CustomNavigationAnimationController: NSObject, UIViewControllerAnimatedTransitioning {

把这个类中添加如下的内容。给这个animation controller使用一个简单的立方体动画。使用了一个reverse变量,用来决定动画的方向。

var reverse: Bool = false

func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
    return 1.5
}

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView()
    let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
    let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
    let toView = toViewController.view
    let fromView = fromViewController.view
    let direction: CGFloat = reverse ? -1 : 1
    let const: CGFloat = -0.005

    toView.layer.anchorPoint = CGPointMake(direction == 1 ? 0 : 1, 0.5)
    fromView.layer.anchorPoint = CGPointMake(direction == 1 ? 1 : 0, 0.5)

    var viewFromTransform: CATransform3D = CATransform3DMakeRotation(direction * CGFloat(M_PI_2), 0.0, 1.0, 0.0)
    var viewToTransform: CATransform3D = CATransform3DMakeRotation(-direction * CGFloat(M_PI_2), 0.0, 1.0, 0.0)
    viewFromTransform.m34 = const
    viewToTransform.m34 = const

    containerView.transform = CGAffineTransformMakeTranslation(direction * containerView.frame.size.width / 2.0, 0)
    toView.layer.transform = viewToTransform
    containerView.addSubview(toView)

    UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
        containerView.transform = CGAffineTransformMakeTranslation(-direction * containerView.frame.size.width / 2.0, 0)
        fromView.layer.transform = viewFromTransform
        toView.layer.transform = CATransform3DIdentity
    }, completion: {
        finished in
        containerView.transform = CGAffineTransformIdentity
        fromView.layer.transform = CATransform3DIdentity
        toView.layer.transform = CATransform3DIdentity
        fromView.layer.anchorPoint = CGPointMake(0.5, 0.5)
        toView.layer.anchorPoint = CGPointMake(0.5, 0.5)

        if (transitionContext.transitionWasCancelled()) {
            toView.removeFromSuperview()
        } else {
            fromView.removeFromSuperview()
        }
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
    })        
}

打开ItemsTableViewController.swift,修改类的声明如下:

class ItemsTableViewController: UITableViewController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {

UINavigationControllerDelegate 来提供animation controller。 
添加如下的属性到类中。

let customNavigationAnimationController = CustomNavigationAnimationController()

在viewDidLoad()中添加如下的代码:

navigationController?.delegate = self

在类中添加如下的方法:

func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    customNavigationAnimationController.reverse = operation == .Pop
    return customNavigationAnimationController
}

这里写图片描述

Making it Interactive

iOS内置的app有这个特征

这里写图片描述

我们需要一个interaction controller。interaction controller使用UIViewControllerInteractiveTransitioning协议。navigation controller delegate或者transitioning delegate,在requesting一个animation controller之后还会requests 一个可选的 interaction controller。 
来创建interaction controller。创建一个新的类叫做CustomInteractionController,继承自UIPercentDrivenInteractiveTransition。 
UIPercentDrivenInteractiveTransition实现了UIViewControllerInteractiveTransitioning协议。 
要想使用UIPercentDrivenInteractiveTransition,你的animation controller必须有一个UIView动画,这样这个动画可以被停止、恢复和播放。 
在这个类中加入如下的代码:

var navigationController: UINavigationController!
var shouldCompleteTransition = false
var transitionInProgress = false
var completionSeed: CGFloat {
    return 1 - percentComplete
}

func attachToViewController(viewController: UIViewController) {
    navigationController = viewController.navigationController
    setupGestureRecognizer(viewController.view)
}

private func setupGestureRecognizer(view: UIView) {
        view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: "handlePanGesture:"))
}

func handlePanGesture(gestureRecognizer: UIPanGestureRecognizer) {
    let viewTranslation = gestureRecognizer.translationInView(gestureRecognizer.view!.superview!)
    switch gestureRecognizer.state {
    case .Began:
        transitionInProgress = true
        navigationController.popViewControllerAnimated(true)
    case .Changed:
        var const = CGFloat(fminf(fmaxf(Float(viewTranslation.x / 200.0), 0.0), 1.0))
        shouldCompleteTransition = const > 0.5
        updateInteractiveTransition(const)
    case .Cancelled, .Ended:
        transitionInProgress = false
        if !shouldCompleteTransition || gestureRecognizer.state == .Cancelled {
            cancelInteractiveTransition()
        } else {
            finishInteractiveTransition()
        }
    default:
        println("Swift switch must be exhaustive, thus the default")
    }
}

使用interaction controller,在ItemsTableViewController.swift,加入如下的属性:

let customInteractionController = CustomInteractionController()

在navigationController(_:animationControllerForOperation: 
fromViewController:toViewController:)方法的开始位置,加入如下的代码:

if operation == .Push {
    customInteractionController.attachToViewController(toVC)
}

然后再加入如下的方法:

func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return customInteractionController.transitionInProgress ? customInteractionController : nil
}

这里写图片描述

download the completed project here

其它参考文章:

另推荐一个开源工具,可以很好的在项目中实现滑动返回: 
FDFullscreenPopGesture

根据以上的例子,我自己写了个交互式的滑动返回的例子。主要的界面如下: 
这里写图片描述 
文章介绍如下:[一个丝滑的全屏滑动返回手势)(http://blog.sunnyxx.com/2015/06/07/fullscreen-pop-gesture/)

定义两个动画类,PushAnimation用来实现Push动画,PopAnimation用来实现PopAnimation,都继承自NSObject,并遵守UIViewControllerAnimatedTransitioning协议。代码如下:

PushAnimation:

    - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        return 0.3;
    }

    - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
    {
        UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        UIView *containerView = [transitionContext containerView];

        [containerView addSubview:toVC.view];
        [containerView addSubview:fromVC.view];
        toVC.view.alpha = 0.5f;

        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
            fromVC.view.transform = CGAffineTransformMakeTranslation(-fromVC.view.bounds.size.width, 0);
            toVC.view.alpha = 1.0;
        } completion:^(BOOL finished) {
            fromVC.view.transform = CGAffineTransformIdentity;
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        }];
    }

PopAnimation:

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return 0.3;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    [[transitionContext containerView] addSubview:toVC.view];
    [[transitionContext containerView] addSubview:fromVC.view];

    toVC.view.transform = CGAffineTransformMakeTranslation(-100, 0);
    toVC.view.alpha = 0.5;

    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         fromVC.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(fromVC.view.frame), 0);
                         toVC.view.alpha = 1.0;
                         toVC.view.transform = CGAffineTransformIdentity;

                     }
                     completion:^(BOOL finished) {
                         fromVC.view.transform = CGAffineTransformIdentity;
                         toVC.view.transform = CGAffineTransformIdentity;

                         if ([transitionContext transitionWasCancelled]) {

                         }
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];
}

要在UINavgationController中实现交互式的滑动返回,需要给UINavgationController的delegate指定对象。自定义个NavigationPerformer类,继承自NSObject,并实现UINavigationControllerDelegate协议。完成代码如下:

@class PushAnimation;
@class PopAnimation;

@interface NavigationPerformer : NSObject<UINavigationControllerDelegate>

@property (weak, nonatomic) IBOutlet UINavigationController *navigationController;

@property (strong, nonatomic) PushAnimation *pushAnimation;

@property (strong, nonatomic) PopAnimation *popAnimation;

@property (strong, nonatomic) UIPercentDrivenInteractiveTransition *interactionController;

@end


@interface NavigationPerformer ()

@property (nonatomic, assign) BOOL shouldCompleteTransition;

@end

@implementation NavigationPerformer

- (void)awakeFromNib
{
    UIPanGestureRecognizer* panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.navigationController.view addGestureRecognizer:panRecognizer];

    self.pushAnimation = [[PushAnimation alloc] init];
    self.popAnimation = [[PopAnimation alloc] init];
}

- (void)pan:(UIPanGestureRecognizer *)pan
{
    UIView *view = self.navigationController.view;
    if (pan.state == UIGestureRecognizerStateBegan) {
        if (self.navigationController.viewControllers.count > 1) {
            self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
            [self.navigationController popViewControllerAnimated:YES];
        }
    }else if(pan.state == UIGestureRecognizerStateChanged){
        CGPoint translation = [pan translationInView:view];
        CGFloat progress = translation.x / CGRectGetWidth(view.bounds);
        progress = MIN(MAX(progress, 0.0), 1.0);
        _shouldCompleteTransition = progress > 0.3;
        [self.interactionController updateInteractiveTransition:progress];
    }else if(pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled){
        if (!_shouldCompleteTransition || pan.state == UIGestureRecognizerStateCancelled) {
            [self.interactionController cancelInteractiveTransition];
        }else{
            [self.interactionController finishInteractiveTransition];
        }
        self.interactionController = nil;
    }
}

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    if (operation == UINavigationControllerOperationPush) {
        return self.pushAnimation;
    }else if(operation == UINavigationControllerOperationPop){
        return self.popAnimation;
    }else{
        return nil;
    }
}

- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
    return self.interactionController;
}

@end

在代码中声明了一个navigationController,用来对当前的UINavigationController保持引用。在Storyboard中给UINavigationController的delegate指定对象。 
在UINavigationController中加入一个object对象,并在其Indentity Inspector中指定Class为NavigationPerformer,如下:

这里写图片描述

指定object对象为UINavigationController的delegate。

这里写图片描述

大概的效果如下: 
这里写图片描述


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值