Mac開發(fā)跬步積累(二):NSViewController 轉(zhuǎn)場動畫精耕細(xì)作

圖片來自網(wǎng)絡(luò)

iOS相比,在macOS中,控制器的轉(zhuǎn)場情景相對要簡潔一些,沒有iOS中導(dǎo)航控制器的PushPop動畫以及邊緣返回手勢, 保留下的Present方式,倒是提供了特有的切換方式, 可以供我們使用出許多效果.

關(guān)于NSViewController基礎(chǔ)細(xì)節(jié),有興趣的同學(xué)可以參考我的Mac開發(fā)基礎(chǔ)教程這個系列的教程,友情提示: 自學(xué)能力好的同學(xué)可以參考github中的課程代碼.另外一門macOS 應(yīng)用開發(fā)進階課程,供有項目經(jīng)驗或?qū)?code>組件化感興趣的同學(xué)參考.

0x00 : extension NSViewController

macOS 10.10之后,關(guān)于NSViewController,蘋果公司專門在一個extension中提供了四個方法用來處理控制器之間的關(guān)系以及切換轉(zhuǎn)場處理.

   1. 內(nèi)嵌在同一個窗口中形式彈出新的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轉(zhuǎn)換到toViewController
    open func transition(from fromViewController: NSViewController, to toViewController: NSViewController, options: NSViewController.TransitionOptions = [], completionHandler completion: (() -> Swift.Void)? = nil)

0x01 : present 與 transition

在上面的系統(tǒng)提供的NSViewController四個方法中,可以分為presenttransition兩種方式:

  • presentXXX: 所有的present方式都是通過調(diào)用 presentViewController(NSViewController, animator: Animator)這個方法來完成展示的,并提供一個遵守NSViewControllerPresentationAnimator協(xié)議的animator控制整個動畫過程.
    <如果希望實現(xiàn)自定義的Present轉(zhuǎn)場效果,可以通過自定義animator方式后面會講到具體實現(xiàn)步驟>

  • transition: 使用一個容器視圖Contain View, 通過addSubViewremoveSubView的方式實現(xiàn)兩個控制器之間的動畫切換展示,系統(tǒng)提供了下面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 細(xì)節(jié):

在進行transition時,所有需要切換的child ViewController必須是同一個 super ViewController,否則會拋出異常錯誤.

  1. transition方法僅支持有父子關(guān)系的控制器結(jié)構(gòu).
  2. transition由父控制器super ViewController進行調(diào)用.
  3. transition僅在子控制器child ViewController之間進行切換.
  4. transition方法中,fromViewcontroller 的視圖必須有superView,否則拋出異常.

0x03: transition Demo

示例代碼: TransAnimationController demo

  1. 搭建UI界面:
構(gòu)建UI界面
  1. 代碼部分:
class ViewController: NSViewController {
 
1. 從Storyboard中的CustomView 連線的控件屬性,用來作為容器視圖,顯示每個ChildViewController的內(nèi)容      
    @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. 設(shè)置容器視圖的顏色
        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 參數(shù),可以實現(xiàn)不同的transition 效果.

0x04 : Present 動畫效果

  • presentViewControllerAsSheet
     @IBAction func presentTest(_ sender: Any) {
        1. 創(chuàng)建控制器
          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 協(xié)議的對象
  2. 實現(xiàn)NSViewControllerPresentationAnimator的兩個方法

public protocol NSViewControllerPresentationAnimator : NSObjectProtocol {
1. present 動畫時,執(zhí)行這個方法,因此在這個方法中實現(xiàn)自定義的動畫效果
    public func animatePresentation(of viewController: NSViewController, from fromViewController: NSViewController)

2. dismiss動畫時,執(zhí)行這個方法 ,在這個方法中可以實在自定義的動畫效果
    public func animateDismissal(of viewController: NSViewController, from fromViewController: NSViewController)
}
  1. 在需要執(zhí)行Present的地方調(diào)用presentViewController(ViewController, animator: )
class PresentAnimator: NSObject {
  
}
// MARK:  NSViewControllerPresentationAnimator
extension PresentAnimator: NSViewControllerPresentationAnimator{
    func animatePresentation(of viewController: NSViewController, from fromViewController: NSViewController) {
        // 這里實現(xiàn)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. 設(shè)置將要顯示視圖的初始frame
        modalView.frame = finalFrame
        modalView.setFrameOrigin(NSMakePoint(finalFrame.origin.x, finalFrame.origin.y - 200))

        5 .添加視圖到容器視圖中
        containerView.addSubview(modalView)

        6. 執(zhí)行動畫效果
        NSAnimationContext.runAnimationGroup({ (animationContext) in
            animationContext.duration = 0.5
            modalView.animator().frame = finalFrame
            
        }, completionHandler: nil)    
    }
    
    func animateDismissal(of viewController: NSViewController, from fromViewController: NSViewController) {
        // 這里實現(xiàn)dismiss時的動畫效果
        1. 獲取開始動畫的frame
        let startFrame = viewController.view.frame
        2. 執(zhí)行動畫
        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(總結(jié))

  1. macOS中,控制器的轉(zhuǎn)場切換無論是presentViewController方式或者transition方式,本質(zhì)上都是將要顯示的控制器視圖View,通過addSubView方法添加到容器視圖中展示.

  2. 通常開發(fā)中如果沒有特殊需求,transition的系統(tǒng)樣式基本都可以滿足使用.

  3. 自定義present 動畫時,需要注意事件穿透問題:

    • 由于顯示出來的控制器視圖(Controller View)是通過addSubView方式添加到容器視圖中,因此在控制器視圖(Controller View)上進行點擊操作,可能會觸發(fā)容器視圖中控件(比如按鈕)的方法

    • 解決辦法: 給容器視圖添加一層背景視圖(自定義的NSView, 重寫mouseDown方法即可),通過背景視圖屏蔽鼠標(biāo)操作,防止事件穿透到容器視圖中

騰訊云+社區(qū)

為了方便更多同學(xué)可以了解到macOS開發(fā)相關(guān)的內(nèi)容,我準(zhǔn)備授權(quán)同步到騰訊云+社區(qū)上,希望大家多多支持...
我的博客即將搬運同步至騰訊云+社區(qū),邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=30okgs5f2aucw

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容