前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS 自定义 ViewController 过渡动画

iOS 自定义 ViewController 过渡动画

作者头像
Alan Zhang
发布2018-10-19 14:50:41
1.3K0
发布2018-10-19 14:50:41
举报
文章被收录于专栏:Alan's LabAlan's Lab

动画预览

相关链接

WWDC 2013 session 218: Custom Transitions Using View Controllers

开扯

最近在朋友圈看到别人转发了一系列很帅的 iOS 交互动画。就想着自己也来玩一下,顺便把之前没写成的 Custom ViewController Transition 自定义视图控制器过渡的文章也一起搞定了。

这里只以这个动画的实现为主线,更系统的介绍请移步上面的相关链接。

视图控制器过渡,就是指图片里那种 ViewController 的过渡效果。(好废话。。。)在上面链接的视频里说到,一共有下面这四个地方可以用自定义过渡:

  • Presentations and dismissals
  • UITabBarController
  • UINavigationController
  • UICollectionViewController layout-to-layout transitions

这个例子里,我们只涉及第二种 UITabBarController

另外还有 Interactive view controller transitions 可交互过渡,例子就是在 NavigationController 的详细页面中从屏幕左侧滑入以返回时的那个动画。可以用手势控制过渡动画的进度,还可以中途取消手势。这个也不会提到。。。

扔了这么多东西不管的好处就是,这篇文章里我们需要处理的新东西就只有两个:

代码语言:javascript
复制
// UITabBarControllerDelegate 的这个方法,用于返回一个负责管理过渡动画的 UIViewControllerAnimatedTransitioning

        optional func tabBarController(_ tabBarController:     UITabBarController,
animationControllerForTransitionFromViewController fromVC: UIViewController,
                                    toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?

// 另一个新东西就是 UIViewControllerAnimatedTransitioning 自己。。。有两个方法需要实现

// 这个方法负责做真正的动画,输入参数是过渡的上下文,从哪个VC过渡到哪个VC这些东西都可以从它得到。
func animateTransition(_ transitionContext: UIViewControllerContextTransitioning)

// 这个返回本过渡的持续时间
func transitionDuration(_ transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval

新建一个 TabBarController 项目,StoryBoard 里简单修饰一下,让两个页面看起来有所不同。

然后在 viewDidLoad 中设置 TabBarControllerdelegate ,这里我们设置成为 self

TabBarController 代码如下:

代码语言:javascript
复制
class MainVC: UITabBarController, UITabBarControllerDelegate { // 实现UITabBarControllerDelegate接口

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self  // delegate设置为self
    }

    /* 如GIF中那样在切换时改变状态栏颜色,这里可选,与过渡无关
    func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
        let index = find(tabBarController.viewControllers! as [UIViewController], viewController)!
        switch index {
        case 1:
            UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true)
        default:
            UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.Default, animated: true)
        }
    }
    */

    // 这里我们只要返回一个UIViewControllerAnimatedTransitioning,tabbarcontroller就会根据它去执行过渡动画
    func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        // 下面会给出TransitioningObject的实现,这里我们可以也可以选择用本方法的fromVC、toVC、tabBarController这三个参数构造出一个TransitioningObject
        let transitionObject: TransitioningObject = TransitioningObject();
        transitionObject.tabBarController = self

        return transitionObject
    }

}

接下来是实际的动画实现,主要的想法是设定一个 CAShapeLayer 作为目标 VC 的遮罩。然后给这个 ShapeLayer 的 path 属性加动画,从半径为0变化到覆盖整个目标 VC 。

代码语言:javascript
复制
class TransitioningObject:NSObject, UIViewControllerAnimatedTransitioning {
    private weak var tabBarController: MainVC!

    // 动画在这里!
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

        // 由context获得参与过渡的各个角色
        let fromView: UIView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let fromViewController: UIViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
        let toView: UIView = transitionContext.viewForKey(UITransitionContextToViewKey)!
        let toViewController: UIViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!

        // 可以吧containerView理解成一个舞台,参与过渡动画的角色在这个舞台上表演。。。所以要让他们上台先。
        transitionContext.containerView().addSubview(fromView)
        transitionContext.containerView().addSubview(toView)

        // 找出各个VC在tabBar上的位置。
        let fromViewControllerIndex = find(self.tabBarController.viewControllers! as [UIViewController], fromViewController)
        let toViewControllerIndex = find(self.tabBarController.viewControllers! as [UIViewController], toViewController)

        // 计算出点击的tab的位置,作为动画的圆心
        let tabBarFrame = tabBarController.tabBar.frame
        let tabBarItemCount = tabBarController.tabBar.items!.count
        let tabBarItemWidth = tabBarFrame.size.width / CGFloat(tabBarItemCount)

        let tappedItemY = tabBarFrame.origin.y
        let tappedItemX = tabBarItemWidth * CGFloat(toViewControllerIndex!) + tabBarItemWidth / 2

        // 圆要放大到的半径,勾股定理算出toView的对角线长度
        var finalRadius = sqrt(pow(toView.frame.height, 2) + pow(toView.frame.width, 2))

        // 构造开始时和结束时的圆的贝赛尔曲线。
        let start = UIBezierPath(ovalInRect: CGRect(x: tappedItemX, y: tappedItemY, width: 0, height: 0)).CGPath
        let final = UIBezierPath(ovalInRect: CGRect(x: tappedItemX - finalRadius, y: tappedItemY - finalRadius, width: finalRadius * 2, height: finalRadius * 2)).CGPath

        // 新建一个CAShapeLayer,用作toView的遮罩。并且开始时的path设置为上面的start——位置在点击的tab上的一个半径为0的圆。
        // 下文中就要给这个path加特技,让他变化到包含整个界面那么大。
        var circleMask = CAShapeLayer()
        circleMask.path = start
        toView.layer.mask = circleMask

        // 给circleMask的path属性加动画
        let animation = CABasicAnimation(keyPath: "path")
        animation.fromValue = start
        animation.toValue = final
        animation.duration = self.transitionDuration(transitionContext)
        animation.delegate = self // 设置CABasicAnimation的delegate为self,好在动画结束后通知系统过渡完成了。
        animation.setValue(transitionContext, forKey: "transitionContext") // 待会需要用到transitionContext的completeTransition方法
        circleMask.addAnimation(animation, forKey: "circleAnimation")
        circleMask.path = final
    }

    // 过渡动画完成后,调用completeTransition说明过渡完成。
    override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
        if let context = anim.valueForKey("transitionContext") as? UIViewControllerContextTransitioning {
            context.completeTransition(true)
        }
    }

    // 持续1秒钟
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return 1;
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 动画预览
  • 相关链接
  • 开扯
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档