Mac開發跬步積累(二):NSViewController 轉場動畫精耕細作

圖片來自網絡

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容