美文网首页iOS DeveloperiOS之MAC端开发Mac OS开发
Mac开发跬步积累(二):NSViewController 转场

Mac开发跬步积累(二):NSViewController 转场

作者: 代码行者 | 来源:发表于2018-07-29 09:55 被阅读188次
    图片来自网络

    iOS相比,在macOS中,控制器的转场情景相对要简洁一些,没有iOS中导航控制器的PushPop动画以及边缘返回手势, 保留下的Present方式,倒是提供了特有的切换方式, 可以供我们使用出许多效果.

    关于NSViewController基础细节,有兴趣的同学可以参考我的Mac开发基础教程这个系列的教程,友情提示: 自学能力好的同学可以参考github中的课程代码.另外一门macOS 应用开发进阶课程,供有项目经验或对组件化感兴趣的同学参考.

    0x00 : extension NSViewController

    macOS 10.10之后,关于NSViewController,苹果公司专门在一个extension中提供了四个方法用来处理控制器之间的关系以及切换转场处理.

       1. 内嵌在同一个窗口中形式弹出新的ViewController
        open func presentViewControllerAsSheet(_ viewController: NSViewController)
        
       2. 新窗口的形式弹出新的ViewController
        open func presentViewControllerAsModalWindow(_ viewController: NSViewController)
    
       3. Popover的形式弹出新的ViewController
        open func presentViewController(_ viewController: NSViewController, asPopoverRelativeTo positioningRect: NSRect, of positioningView: NSView, preferredEdge: NSRectEdge, behavior: NSPopover.Behavior)
    
       4. 从fromViewController转换到toViewController
        open func transition(from fromViewController: NSViewController, to toViewController: NSViewController, options: NSViewController.TransitionOptions = [], completionHandler completion: (() -> Swift.Void)? = nil)
    

    0x01 : present 与 transition

    在上面的系统提供的NSViewController四个方法中,可以分为presenttransition两种方式:

    • presentXXX: 所有的present方式都是通过调用 presentViewController(NSViewController, animator: Animator)这个方法来完成展示的,并提供一个遵守NSViewControllerPresentationAnimator协议的animator控制整个动画过程.
      <如果希望实现自定义的Present转场效果,可以通过自定义animator方式后面会讲到具体实现步骤>

    • transition: 使用一个容器视图Contain View, 通过addSubViewremoveSubView的方式实现两个控制器之间的动画切换展示,系统提供了下面8中过渡动画方式:

     @available(OSX 10.10, *)
        public struct TransitionOptions : OptionSet {
    
            public static var crossfade: NSViewController.TransitionOptions { get }
            
            public static var slideUp: NSViewController.TransitionOptions { get }
    
            public static var slideDown: NSViewController.TransitionOptions { get }
    
            public static var slideLeft: NSViewController.TransitionOptions { get }
    
            public static var slideRight: NSViewController.TransitionOptions { get }
    
            public static var slideForward: NSViewController.TransitionOptions { get }
    
            public static var slideBackward: NSViewController.TransitionOptions { get }
    
            public static var allowUserInteraction: NSViewController.TransitionOptions { get }
        }
    
    crossfade 效果
    crossfade效果
    slideUp/slideDown 效果
    slideUp/slideDown 效果
    slideLeft/slideRight (slideForward/slideBackward ) 效果
    slideLeft/slideRight (slideForward/slideBackward ) 效果
    allowUserInteraction 效果
    allowUserInteraction 效果

    0x02 : transition 细节:

    在进行transition时,所有需要切换的child ViewController必须是同一个 super ViewController,否则会抛出异常错误.

    1. transition方法仅支持有父子关系的控制器结构.
    2. transition由父控制器super ViewController进行调用.
    3. transition仅在子控制器child ViewController之间进行切换.
    4. transition方法中,fromViewcontroller 的视图必须有superView,否则抛出异常.

    0x03: transition Demo

    示例代码: TransAnimationController demo

    1. 搭建UI界面:
    构建UI界面
    1. 代码部分:
    class ViewController: NSViewController {
     
    1. 从Storyboard中的CustomView 连线的控件属性,用来作为容器视图,显示每个ChildViewController的内容      
        @IBOutlet weak var containView: MYContainView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
    2. 添加需要切换的子控制器: RedController 和BlueController 为自定义的两个控制器,仅显示不同的视图颜色.
            addChildViewController(RedController())
            addChildViewController(BlueController())
    3. 需要将第一个ChildViewController的view添加到容器视图中;
            containView.addSubview(childViewControllers[0].view)
    4. 设置容器视图的颜色
            containView.layer?.backgroundColor = NSColor.orange.cgColor
        
        }
    5. 点击下一个按钮, 从RedController 切换到BlueController
        @IBAction func clickBtn(_ sender: Any) {
            transition(from: childViewControllers[0], to: childViewControllers[1], options: .slideLeft, completionHandler: nil)
        }
    6. 点击上一个按钮, 从BlueController 切换到RedController
        @IBAction func clickUpButton(_ sender: Any) {
            transition(from: childViewControllers[1], to: childViewControllers[0], options: .slideRight, completionHandler: nil)
        }
    }
    6. 修改4,5 步骤中的option 参数,可以实现不同的transition 效果.
    

    0x04 : Present 动画效果

    • presentViewControllerAsSheet
       @IBAction func presentTest(_ sender: Any) {
          1. 创建控制器
            let greenVC = GreenController()
         2. 以AsSheet方式弹出控制器
            presentViewControllerAsSheet(greenVC)
        }
      
    AsSheet
    • presentViewControllerAsModalWindow
     @IBAction func presentTest(_ sender: Any) {
            let greenVC = GreenController()
           1. 以AsSheet方式弹出控制器
            presentViewControllerAsModalWindow(greenVC)
        }
    
    AsModalWindow
    • presentViewControllerAsPopover
            let greenVC = GreenController()
           1. 以Popover方式弹出控制器
            presentViewController(greenVC, asPopoverRelativeTo: sender.bounds, of: sender, preferredEdge: NSRectEdge.maxX, behavior: NSPopover.Behavior.transient)
    
    Jul-28-2018 20-56-14.gif

    0x05 Present 自定义动画( 划重点)

    1. 自定义一个遵守NSViewControllerPresentationAnimator 协议的对象
    2. 实现NSViewControllerPresentationAnimator的两个方法
    
    public protocol NSViewControllerPresentationAnimator : NSObjectProtocol {
    1. present 动画时,执行这个方法,因此在这个方法中实现自定义的动画效果
        public func animatePresentation(of viewController: NSViewController, from fromViewController: NSViewController)
    
    2. dismiss动画时,执行这个方法 ,在这个方法中可以实在自定义的动画效果
        public func animateDismissal(of viewController: NSViewController, from fromViewController: NSViewController)
    }
    
    1. 在需要执行Present的地方调用presentViewController(ViewController, animator: )
    class PresentAnimator: NSObject {
      
    }
    // MARK:  NSViewControllerPresentationAnimator
    extension PresentAnimator: NSViewControllerPresentationAnimator{
        func animatePresentation(of viewController: NSViewController, from fromViewController: NSViewController) {
            // 这里实现present的动画效果
            /**viewController: 将要被present出来的视图控制器, fromViewcontroller --> presented动作 ---> viewController */
            1. 获取容器view
            let containerView = fromViewController.view
            
            2. 计算最终显示的frame
            let finalFrame = NSInsetRect(containerView.bounds, 50, 50)
            3. 需要显示的view
            let modalView = viewController.view
            
            4. 设置将要显示视图的初始frame
            modalView.frame = finalFrame
            modalView.setFrameOrigin(NSMakePoint(finalFrame.origin.x, finalFrame.origin.y - 200))
    
            5 .添加视图到容器视图中
            containerView.addSubview(modalView)
    
            6. 执行动画效果
            NSAnimationContext.runAnimationGroup({ (animationContext) in
                animationContext.duration = 0.5
                modalView.animator().frame = finalFrame
                
            }, completionHandler: nil)    
        }
        
        func animateDismissal(of viewController: NSViewController, from fromViewController: NSViewController) {
            // 这里实现dismiss时的动画效果
            1. 获取开始动画的frame
            let startFrame = viewController.view.frame
            2. 执行动画
            NSAnimationContext.runAnimationGroup({ (animationContext) in
                animationContext.duration = 0.5
                viewController.view.animator().setFrameOrigin(NSMakePoint(startFrame.origin.x, startFrame.origin.y - fromViewController.view.bounds.size.height - 100))
            }) {
           3. 动画完成后,移除子视图
                viewController.view.removeFromSuperview()
            }
        }
    }
    
    • 示例效果:
    自定义present 动画效果

    Summary(总结)

    1. macOS中,控制器的转场切换无论是presentViewController方式或者transition方式,本质上都是将要显示的控制器视图View,通过addSubView方法添加到容器视图中展示.

    2. 通常开发中如果没有特殊需求,transition的系统样式基本都可以满足使用.

    3. 自定义present 动画时,需要注意事件穿透问题:

      • 由于显示出来的控制器视图(Controller View)是通过addSubView方式添加到容器视图中,因此在控制器视图(Controller View)上进行点击操作,可能会触发容器视图中控件(比如按钮)的方法

      • 解决办法: 给容器视图添加一层背景视图(自定义的NSView, 重写mouseDown方法即可),通过背景视图屏蔽鼠标操作,防止事件穿透到容器视图中

    腾讯云+社区

    为了方便更多同学可以了解到macOS开发相关的内容,我准备授权同步到腾讯云+社区上,希望大家多多支持...
    我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=30okgs5f2aucw

    相关文章

      网友评论

        本文标题:Mac开发跬步积累(二):NSViewController 转场

        本文链接:https://www.haomeiwen.com/subject/rwapyftx.html