與
iOS
相比,在macOS
中,控制器的轉場情景相對要簡潔一些,沒有iOS
中導航控制器的Push
和Pop
動畫以及邊緣返回手勢, 保留下的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
四個方法中,可以分為present
和transition
兩種方式:
presentXXX: 所有的present方式都是通過調用
presentViewController(NSViewController, animator: Animator)
這個方法來完成展示的,并提供一個遵守NSViewControllerPresentationAnimator
協議的animator
控制整個動畫過程.
<如果希望實現自定義的Present轉場效果,可以通過自定義animator方式后面會講到具體實現步驟
>transition: 使用一個容器視圖
Contain View
, 通過addSubView
和removeSubView
的方式實現兩個控制器之間的動畫切換展示,系統提供了下面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 細節:
在進行
transition
時,所有需要切換的child ViewController
必須是同一個super ViewController
,否則會拋出異常錯誤.
-
transition
方法僅支持有父子關系
的控制器結構. -
transition
由父控制器super ViewController
進行調用. -
transition
僅在子控制器child ViewController
之間進行切換. -
transition
方法中,fromViewcontroller
的視圖必須有superView
,否則拋出異常.
0x03: transition Demo
- 搭建UI界面:
- 代碼部分:
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) }
- 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
協議的對象 - 實現
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)
}
- 在需要執行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()
}
}
}
- 示例效果:
Summary(總結)
在
macOS
中,控制器的轉場切換無論是presentViewController
方式或者transition
方式,本質上都是將要顯示的控制器視圖View
,通過addSubView
方法添加到容器視圖中展示.通常開發中如果沒有特殊需求,
transition
的系統樣式基本都可以滿足使用.-
自定義
present
動畫時,需要注意事件穿透
問題:由于顯示出來的
控制器視圖(Controller View)
是通過addSubView
方式添加到容器視圖中,因此在控制器視圖(Controller View)
上進行點擊操作,可能會觸發容器視圖中控件(比如按鈕)的方法解決辦法: 給容器視圖添加一層
背景視圖(自定義的NSView, 重寫mouseDown方法即可)
,通過背景視圖屏蔽鼠標操作,防止事件穿透到容器視圖中
騰訊云+社區
為了方便更多同學可以了解到macOS開發相關的內容,我準備授權同步到騰訊云+社區上,希望大家多多支持...
我的博客即將搬運同步至騰訊云+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=30okgs5f2aucw