iOS 8 Presentation Controller

本文搬运自 Dative Studios,作者 Pete Callaway

iOS 8 新加入一个类:UIPresentationController,它与 iOS 7 新添加的几个类与协议一道,帮助我们方便快捷地实现 ViewController 的自定义过渡效果。我发现要搞懂一个新的 API,最快的方法还是写一个例子。废话不多说,上例子!

Header

该项目在GitHub可以下载

实现自定义过渡

我们需要两个对象来实现自定义过渡,一个 UIPresentationController 的子类以及一个遵从 UIViewControllerAnimatedTransitioning 协议的类。

我们的 UIPresentationController 的子类是负责「被呈现」及「负责呈现」的 controller 以外的 controller 的,看着很绕口,说白了,在我们的例子中,它负责的仅仅是那个带渐变效果的黑色半透明背景 View。

而 UIViewControllerAnimatedTransitioning 类将会负责「被呈现」的 ViewController 的过渡动画。

首先我们看 UIPresentationController

UIPresentationController

在我们的 UIPresentationController 中,我们需要重写其中5个方法:

  • presentationTransitionWillBegin
  • presentationTransitionDidEnd:
  • dismissalTransitionWillBegin
  • dismissalTransitionDidEnd:
  • frameOfPresentedViewInContainerView

presentationTransitionWillBegin 是在呈现过渡即将开始的时候被调用的。我们在这个方法中把半透明黑色背景 View 加入到 containerView 中,并且做一个 alpha 从0到1的渐变过渡动画。

override func presentationTransitionWillBegin() {
    // 添加半透明背景 View 到我们的视图结构中
    self.dimmingView.frame = self.containerView.bounds
    self.dimminView.alpha = 0.0

    self.containerView.addSubview(self.dimmingView)
    self.containerView.addSubview(self.presentedView())

    // 与过渡效果一起执行背景 View 的淡入效果
    let transitionCoordinator = self.presentingViewController.transitionCoordinator()
    transitionCoordinator.animateAlongsideTransition({(context: UIViewControllerTransitionCoordinatorContext!) -> Void in
        self.dimmingView.alpha  = 1.0
    }, completion:nil)
}

通过使用「负责呈现」的 controller 的 UIViewControllerTransitionCoordinator,我们可以确保我们的动画与其他动画一道儿播放。

presentationTransitionDidEnd: 是在呈现过渡结束时被调用的,并且该方法提供一个布尔变量来判断过渡效果是否完成。在我们的例子中,我们可以使用它在过渡效果已结束但没有完成时移除半透明的黑色背景 View。

override func presentationTransitionDidEnd(completed: Bool)  {
    // 如果呈现没有完成,那就移除背景 View
    if !completed {
        self.dimmingView.removeFromSuperview()
    }
}

以上就涵盖了我们的背景 View 的呈现部分,我们现在需要给它添加淡出动画并且在它消失后移除它。正如你预料的那样,dismissalTransitionWillBegin 正是我们把它的 alpha 重新设回0的地方。

override func dismissalTransitionWillBegin()  {
    // 与过渡效果一起执行背景 View 的淡出效果
    let transitionCoordinator = self.presentingViewController.transitionCoordinator()
    transitionCoordinator.animateAlongsideTransition({(context: UIViewControllerTransitionCoordinatorContext!) -> Void in
        self.dimmingView.alpha  = 0.0
    }, completion:nil)
}

我们还需要在消失完成后移除背景 View。做法与上面 presentationTransitionDidEnd: 类似,我们重载 dismissalTransitionDidEnd: 方法

override func dismissalTransitionDidEnd(completed: Bool) {
    // 如果消失没有完成,那么把背景 View 移除
    if completed {
        self.dimmingView.removeFromSuperview()
    }
}

还有最后一个方法需要重载。在我们的自定义呈现中,被呈现的 view 并没有完全完全填充整个屏幕,而是很小的一个矩形。被呈现的 view 的过渡动画之后的最终位置,是由 UIPresentationViewController 来负责定义的。我们重载 frameOfPresentedViewInContainerView 方法来定义这个最终位置

override func frameOfPresentedViewInContainerView() -> CGRect {
    // 我们可不希望被呈现的 View 占据了整个屏幕,所以我们调整他的frame
    var frame = self.containerView.bounds;
    frame = CGRectInset(frame, 50.0, 200.0)

    return frame
}

最终完整的类可以在此浏览。

UIViewControllerAnimatedTransitioning

我之前已经在上一篇博文中提到了 UIViewControllerAnimatedTransitioning 协议的使用。感谢 iOS 8 新加入的 UIPresentationController, 我们的 UIViewControllerAnimatedTransitioning 类可以比之前少做一些事了,现在,它只负责与呈现相关的 ViewController 的 View 的动画了,其他额外的 View 一概再管了,比如我们的黑色背景 View。

我们需要实现两个协议方法

  • transitionDuration:
  • animatedTransition:

我们还需要放一个类的 property,这样我们可以在类的初始化方法中定义它,用来区别我们到底是让 ViewController 呈现还是消失。

transitionDuration: 方法比较简单,我们要做的仅仅是返回一个动画持续时长。

在 animateTransition: 方法中,我们给被呈现的 view 添加呈现与消失的动画。

func animateTransition(transitionContext: UIViewControllerContextTransitioning!)  {
    if isPresenting {
        animatePresentationWithTransitionContext(transitionContext)
    }
    else {
        animateDismissalWithTransitionContext(transitionContext)
    }
}
func animatePresentationWithTransitionContext(transitionContext: UIViewControllerContextTransitioning) {
    let presentedController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
    let presentedControllerView = transitionContext.viewForKey(UITransitionContextToViewKey)!
    let containerView = transitionContext.containerView()!

    // 设定被呈现的 view 一开始的位置,在屏幕下方
    presentedControllerView.frame = transitionContext.finalFrameForViewController(presentedController)
    presentedControllerView.frame.origin.y = containerView.bounds.size.height

    containerView.addSubview(presentedControllerView)

    // 添加一个动画,让被呈现的 view 移动到最终位置,我们使用0.6的damping值让动画有一种duang-duang的感觉……
    UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.0, options: .AllowUserInteraction, animations: {
        presentedControllerView.center.y = containerView.bounds.size.height/2
    }, completion: {(completed: Bool) -> Void in
        transitionContext.completeTransition(completed)
    })
}

func animateDismissalWithTransitionContext(transitionContext: UIViewControllerContextTransitioning) {
    let presentedControllerView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
    let containerView = transitionContext.containerView()!

    // 添加一个动画,让要消失的 view 向下移动,离开屏幕
    UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .AllowUserInteraction, animations: {
        presentedControllerView.frame.origin.y = containerView.bounds.size.height
    }, completion: {(completed: Bool) -> Void in
            transitionContext.completeTransition(completed)
    })
}

完整的类可以在此浏览。

使用自定义呈现类

我们已经实现了我们所需的实现自定义呈现的类。接下来我们看看怎么使用它们。事实上,有许多种不同的方法来使用它们,不过最简单的方法还是让被呈现的 ViewController 来作为自己的 UIViewControllerTransitioningDelegage

required init(coder aDecoder: NSCoder!) {
    super.init(coder: aDecoder)
    self.commonInit()
}

override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!)  {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    self.commonInit()
}

func commonInit() {
    self.modalPresentationStyle = .Custom
    //让被呈现 viewController 自己来作为自己的 transitioningDelegate
    self.transitioningDelegate = self
}

现在我们的 ViewController 就可以在呈现自己时,提供给我们一个 UIPresentationController 的实例了。

func presentationControllerForPresentedViewController(presented: UIViewController!, presentingViewController presenting: UIViewController!, sourceViewController source: UIViewController!) -> UIPresentationController! {
    if presented == self {
        return CustomPresentationController(presentedViewController: presented, presentingViewController: presenting)
    }
    else {
        return nil
    }
}

当然,还要有一个遵从了 UIViewControllerAnimatedTransitioning 协议的类的实例来负责动画

func animationControllerForPresentedController(presented: UIViewController!, presentingController presenting: UIViewController!, sourceController source: UIViewController!) -> UIViewControllerAnimatedTransitioning! {
    if presented == self {
        return CustomPresentationAnimationController(isPresenting: true)
    }
    else {
        return nil
    }
}

func animationControllerForDismissedController(dismissed: UIViewController!) -> UIViewControllerAnimatedTransitioning! {
    if dismissed == self {
        return CustomPresentationAnimationController(isPresenting: false)
    }
    else {
        return nil
    }
}

完整的类可以在此浏览。

尾声

就像我上篇博文中说的那样,希望这篇文章能帮你创建自己的自定义呈现效果。文中例子的工程文件已上传至 GitHub

本文搬运自 Dative Studios,作者 Pete Callaway

2014-09-21 09:085247
  • jackyccaa2014-07-24 07:51

    在xcode6 beta4工程无法编译通过

  • nonomori2014-07-24 11:33

    @jackyccaa 已更新

  • 极分享2016-03-01 17:18

    非常棒啊 ..........