
與
iOS相比,在macOS中,控制器的轉(zhuǎn)場情景相對要簡潔一些,沒有iOS中導(dǎo)航控制器的Push和Pop動畫以及邊緣返回手勢, 保留下的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四個方法中,可以分為present和transition兩種方式:
presentXXX: 所有的present方式都是通過調(diào)用
presentViewController(NSViewController, animator: Animator)這個方法來完成展示的,并提供一個遵守NSViewControllerPresentationAnimator協(xié)議的animator控制整個動畫過程.
<如果希望實現(xiàn)自定義的Present轉(zhuǎn)場效果,可以通過自定義animator方式后面會講到具體實現(xiàn)步驟>transition: 使用一個容器視圖
Contain View, 通過addSubView和removeSubView的方式實現(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 效果

slideUp/slideDown 效果

slideLeft/slideRight (slideForward/slideBackward ) 效果

allowUserInteraction 效果

0x02 : transition 細(xì)節(jié):
在進行
transition時,所有需要切換的child ViewController必須是同一個super ViewController,否則會拋出異常錯誤.
-
transition方法僅支持有父子關(guān)系的控制器結(jié)構(gòu). -
transition由父控制器super ViewController進行調(diào)用. -
transition僅在子控制器child ViewController之間進行切換. -
transition方法中,fromViewcontroller的視圖必須有superView,否則拋出異常.
0x03: transition Demo
- 搭建UI界面:

- 代碼部分:
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) }

- presentViewControllerAsModalWindow
@IBAction func presentTest(_ sender: Any) {
let greenVC = GreenController()
1. 以AsSheet方式彈出控制器
presentViewControllerAsModalWindow(greenVC)
}

- presentViewControllerAsPopover
let greenVC = GreenController()
1. 以Popover方式彈出控制器
presentViewController(greenVC, asPopoverRelativeTo: sender.bounds, of: sender, preferredEdge: NSRectEdge.maxX, behavior: NSPopover.Behavior.transient)

0x05 Present 自定義動畫( 劃重點)
- 自定義一個遵守
NSViewControllerPresentationAnimator協(xié)議的對象 - 實現(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)
}
- 在需要執(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()
}
}
}
- 示例效果:

Summary(總結(jié))
在
macOS中,控制器的轉(zhuǎn)場切換無論是presentViewController方式或者transition方式,本質(zhì)上都是將要顯示的控制器視圖View,通過addSubView方法添加到容器視圖中展示.通常開發(fā)中如果沒有特殊需求,
transition的系統(tǒng)樣式基本都可以滿足使用.-
自定義
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