macOS App层次结构、使用WindowController
首先要说明iOS使用的Cocoa Touch框架,而macOS使用Cocoa框架!相比之下macOS的App的层次结构稍微复杂一些~
咱先创建好macOS的App后,默认产生的'Main.storyboard'含有了三个层次:Application Scene、Window Controller Scene、View Controller Scene。
Mac App标准结层次构:Application → (WindowController→Window) → (ViewController→View)
Application Scene层次
Application SceneWindow Controller Scene层次
Window Controller SceneView Controller Scene层次
View Controller Scene
而Window/WindowController和View/ViewController这四种实例是最常使用的!每处理一个窗口(Window)就会对这2个层次进行操作~
为了方便展示层次关系在代码上的体现,把'Main.storyboard'中的Window Controller Scene层次和View Controller Scene层次都删除掉(保留Application Scene层次-顶部的菜单栏还不错),并把ViewController.Swift也删除掉。
项目的入口还是'Main.storyboard'。
自己重新书写Window Controller Scene层次和View Controller Scene层次!
先自己重新创建一个继承自NSViewController名字为MainViewController的类:
继承自NSViewController名字为MainViewController的类通过'.xib'文件中控件的IBOutlet、IBAction,完成如下代码的配置:
通过'.xib'文件,完成代码的配置在"AppDelegate.Swift"文件中:
var mainWC: NSWindowController?//添加窗口控制器
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
//let mainVC = NSViewController()//❌❌报错:-[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: NSViewController in bundle (null).
//必须使用‘NSNib’形式的NSViewController类——含'xib'的
//let mainVC = MainViewController()
let mainVC = MainViewController(nibName: "MainViewController", bundle: Bundle.main)
let window = NSWindow(contentViewController: mainVC)
mainWC = NSWindowController(window: window)
//mainVC.myWC = mainWC //对应的窗口——设置与否可选
mainWC?.showWindow(nil)
}
注意:
a.必须使用‘NSNib’形式的NSViewController类实例(如上含'xib'的),否则运行报错“-[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: NSViewController in bundle (null).
”!
b.通过NSWindow(contentViewController: mainVC)
创建window
(窗口)时,确定window
(窗口)和mainVC
(视图控制器)的联系——设置contentViewController
属性;通过NSWindowController(window: window)
创建mainWC
(窗口控制器)时,确定mainWC
(窗口控制器)和window
(窗口)的联系!
c.通过.showWindow(nil)
方法,展示mainWC
(窗口控制器)!
效果:展示该视图控制器(MainViewController
类实例),点击按钮进行相应的响应~
这样就可以使用自定义的视图控制器了(窗口控制器也可以自定义)!
Window层的使用(NSWindow、NSWindowController)
大概讲一下NSWindow和NSWindowController的基础属性~
演示代码书写如下:
@objc func clickOneButton() {
//(窗口风格)NSWindow.StyleMask - 首项:borderless
//(存储类型)NSWindow.BackingStoreType - 首项:retained
let widnow = NSWindow(contentRect: NSMakeRect(100, 100, 300, 200), styleMask: NSWindow.StyleMask.titled, backing: NSWindow.BackingStoreType.buffered, defer: false)
print("create a window")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) {
print("delay after 2s","设置内容视图contentView的背景色为cyan")
widnow.contentView?.wantsLayer = true
widnow.contentView?.layer?.backgroundColor = NSColor .cyan.cgColor
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) {
print("delay after 4s","让该窗口居于屏幕中央")
widnow .center()//让该窗口居于屏幕中央
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) {
print("delay after 6s","设置该窗口的标题")
widnow.title = "标题title"
//if #available(OSX 11.0, *) {
// widnow.subtitle = "subtitle 123456"
//}
}
}
}
let myWC = NSWindowController(); myWC.window = widnow
//let myWC = NSWindowController(window: widnow) //简便写法
myWC .showWindow(nil)//展示窗口
}
func addOneClickButton() {
let oneBtn = NSButton(frame: NSMakeRect(100, 100, 100, 100))
self.view .addSubview(oneBtn)
oneBtn.target = self; oneBtn.action = #selector(clickOneButton)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self .addOneClickButton()
}
效果:运行项目后,点击‘Button’按钮时——创建一个(相对于屏幕)frame为(100, 100, 300, 200)的窗口,延时2s后设置内容视图(contentView
)的背景色为cyan,再延时2s后让该窗口居于屏幕中央,再延时2s后设置该窗口的标题为"标题title"!每当再次点击‘Button’按钮,继续重复执行上面的一系列操作(创建窗口,延时2s后操作、再延时2s后操作、再延时2s后操作)!
关于NSWindow和NSWindowController基础属性的更多详细信息,请参考:
NSWindow:https://developer.apple.com/documentation/appkit/nswindow
NSWindowController:https://developer.apple.com/documentation/appkit/nswindowcontroller
WindowController层的特殊情况
-
1.创建NSWindow(窗口)所使用的视图控制器(NSViewController),必须是使用‘NSNib’形式的NSViewController类实例(如上含'xib'的),否则运行报错“
-[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: NSViewController in bundle (null).
”!直接使用工程原生的'Main.storyboard'对应的ViewController~ 通过该'Main.storyboard'文件中控件的IBOutlet、IBAction,完成如下代码的配置:
完成代码的配置-- 使用如下代码创建并展示窗口控制器(NSWindowController)实例:窗口控制器
contentViewController
属性是NSViewController
类型!@IBAction func leftBtnClick(_ sender: Any) { let sub1VC = NSViewController()//❌//[General] -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: NSViewController in bundle (null). let sub1Window = NSWindow(contentViewController: sub1VC) sub1WC = NSWindowController(window: sub1Window) sub1WC?.window?.title = "Left Window"//设置标题 sub1WC? .showWindow(nil) }
运行时,点击'Left'按钮会报错:
[General] -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: NSViewController in bundle (null).
-- 使用如下代码创建并展示窗口控制器(NSWindowController)实例:窗口控制器
未勾选'Also create XIB file for user interface'contentViewController
属性是 自定义但未使用‘NSNib’形式的WrongSub1ViewController
类型!@IBAction func leftBtnClick(_ sender: Any) { let sub1VC = WrongSub1ViewController()//❌//[General] -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: MacDealWindow.WrongSub1ViewController in bundle (null). let sub1Window = NSWindow(contentViewController: sub1VC) sub1WC = NSWindowController(window: sub1Window) sub1WC?.window?.title = "Left Window"//设置标题 sub1WC? .showWindow(nil) }
运行时,点击'Left'按钮会报错:
[General] -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: MacDealWindow.WrongSub1ViewController in bundle (null).
解决方法:
【一】必须使用‘NSNib’形式的NSViewController类型的实例作为窗口控制器的
勾选'Also create XIB file for user interface'contentViewController
——含'xib'的
使用如下代码创建并展示窗口控制器(NSWindowController)实例:窗口控制器contentViewController
属性是 自定义且使用‘NSNib’形式的Sub1ViewController
类型!@IBAction func leftBtnClick(_ sender: Any) { let sub1VC = Sub1ViewController(nibName: "Sub1ViewController", bundle: Bundle.main) //let sub1VC = Sub1ViewController()//✅直接初始化 let sub1Window = NSWindow(contentViewController: sub1VC) sub1WC = NSWindowController(window: sub1Window) sub1WC?.window?.title = "Left Window"//设置标题 sub1WC? .showWindow(nil) }
效果:运行时,点击'Left'按钮—1.创建一个标题为"Left Window"的窗口、2.不会报错!
【二】直接使用‘NSNib’形式的NSWindowController类型的实例,作为窗口控制器!
勾选'Also create XIB file for user interface'
使用如下代码创建并展示窗口控制器(NSWindowController)实例:窗口控制器是 自定义且使用‘NSNib’形式的LeftWindowController
类型!该窗口控制器类型 — LeftWindowController:
'LeftWindowController.swift'文件 'LeftWindowController.xib'文件@IBAction func leftBtnClick(_ sender: Any) { //【二】直接使用‘NSNib’形式的NSWindowController,作为窗口控制器 sub1WC = NSWindowController(windowNibName: "LeftWindowController") //sub1WC = NSWindowController() //无反应 //系统默认窗口控制器 //sub1WC = LeftWindowController2()//无反应 //未使用‘NSNib’形式的窗口控制器 sub1WC?.window?.title = "Left Window"//设置标题 sub1WC? .showWindow(nil) }
效果:运行时,点击'Left'按钮—1.创建一个标题为"Left Window"的窗口、2.不会报错!
对于“
未勾选'Also create XIB file for user interface'//sub1WC = LeftWindowController2()//无反应 //未使用‘NSNib’形式的窗口控制器
”代码,其所使用自定义的LeftWindowController2
类型的窗口控制器 没有使用‘NSNib’形式!更多属性的效果,通过'
.window
'进行访问和设置~
-
2.NSWindow代理的使用——
NSWindowDelegate
(继续上面代码逻辑)
实现代码如下://MARK:NSWindowDelegate func windowWillMove(_ notification: Notification) { print("notification.object:\(String(describing: notification.object))") let nowWindow = notification.object; print("windowWillMove nowWindow:\(nowWindow as Any)") } func windowWillMiniaturize(_ notification: Notification) { print("windowWillMiniaturize") } func windowDidBecomeMain(_ notification: Notification) { print("windowDidBecomeMain") } func windowDidResignMain(_ notification: Notification) { print("windowDidResignMain") } var sub1WC: NSWindowController? @IBAction func leftBtnClick(_ sender: Any) { //【二】直接使用‘NSNib’形式的NSWindowController,作为窗口控制器 sub1WC = NSWindowController(windowNibName: "LeftWindowController") sub1WC?.window?.delegate = self//设置代理 sub1WC? .showWindow(nil) print("window:\(sub1WC?.window as Any)") }
效果:运行时,点击'Left'按钮—创建一个标题为"Left Window"的window窗口!
1.移动该window窗口时,会回调windowWillMove
方法、
2.分别选择项目默认ViewController的窗口和该window窗口时进行切换,选中该window窗口时—回调windowDidBecomeMain
方法/取消选中该window窗口时—回调windowDidResignMain
方法、
3.点击该窗口左侧的最小化按钮时—回调windowWillMiniaturize
方法和windowDidResignMain
方法,再在Dock栏选择该window窗口实现最大化时—回调windowDidBecomeMain
方法!注意:通过代理方法返回的
(_ notification: Notification)
,使用notification
可获取到当前 响应操作的窗口(该window窗口)——notification.object
let nowWindow = notification.object;//获取当前 响应操作的窗口
更多请参考:NSWindowDelegate——https://developer.apple.com/documentation/appkit/nswindowdelegate
-
3.使用模态 — 指定窗口之外的其他窗口 不可操作!(继续上面代码逻辑)
核心方法:NSApplication .shared .runModal(for: 窗口对象)
-开始模态、NSApplication .shared .stopModal()
-结束模态~
在'LeftWindowController.swift'文件中:import Cocoa class LeftWindowController: NSWindowController { override func awakeFromNib() { super .awakeFromNib() print("LeftWindowController awakeFromNib") //self .addOneBtn()//避免重复添加 } override func windowDidLoad() { super.windowDidLoad() // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. print("LeftWindowController windowDidLoad") self .addOneBtn() } func addOneBtn() {//为该window,随便添加一个按钮 let btn = NSButton(frame: NSMakeRect(100, 100, 100, 100)) btn.target = self; btn.action = #selector(clickBtn) self.window?.contentView! .addSubview(btn) } @objc func clickBtn() { print("clickBtn") //打开了模态后,’DispatchQueue.main.asyncAfter‘延时操作不被执行~ //DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) { // print("LeftWindowController内——延时2s操作,不会被调用") //} print("LeftWindowController内——结束模态 \(NSDate())") NSApplication .shared .stopModal()//结束模态 } }
在'ViewController.swift'中:
import Cocoa class ViewController: NSViewController ,NSWindowDelegate { //MARK:NSWindowDelegate func windowWillClose(_ notification: Notification) { print("windowWillClose") print("windowWillClose 结束模态 \(NSDate())") NSApplication .shared .stopModal()//结束模态 }//窗口关闭的响应——因为sub1WC的window窗口执行了’.close()‘方法(在LeftWindowController.swift文件中) override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } var sub1WC: LeftWindowController? @IBAction func leftBtnClick(_ sender: Any) { //【二】直接使用‘NSNib’形式的NSWindowController,作为窗口控制器 sub1WC = LeftWindowController(windowNibName: "LeftWindowController") //sub1WC = LeftWindowController()//❌//没有window窗口被创建 sub1WC?.window?.delegate = self; sub1WC? .showWindow(self) //sub1WC?.loadWindow()//加载window窗口 print(sub1WC as Any) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) { [self] in print("开始模态") NSApplication .shared .runModal(for: (sub1WC?.window)!)//开始模态-无法到其他窗口操作 //模态结束后,才会继续执行’DispatchQueue.main.asyncAfter‘延时 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0) { print("结束模态 \(NSDate())") NSApplication .shared .stopModal()//结束模态 } } } @IBAction func rightBtnClick(_ sender: Any) { } override var representedObject: Any? { didSet { // Update the view, if already loaded. } } }
效果1:运行成功后默认的窗口A可以进行正常操作;点击'Left'按钮创建新窗口B后窗口A还可以正常操作,但延时2s后开始模态就只有新窗口B可以进行操作(默认的窗口A就不可进行操作了);点击'Button'按钮结束模态后默认的窗口A又可以进行操作了!
直接调用‘NSApplication .shared .stopModal()//结束模态’的方法注:在'LeftWindowController.swift'文件中将模态结束后,(默认的'ViewController.swift'中)才会继续执行’
DispatchQueue.main.asyncAfter
‘延时!
效果2:运行成功后默认的窗口A可以进行正常操作;点击'Left'按钮创建新窗口B后窗口A还可以正常操作,但延时2s后开始模态就只有新窗口B可以进行操作(默认的窗口A就不可进行操作了);点击新窗口B的左上方‘关闭’按钮后在windowWillClose
回调方法中结束模态后默认的窗口A又可以进行操作了!对应的OC代码:
[[NSApplication sharedApplication] runModalForWindow:self.userLoginWinodwC.window];//开始模态-无法到其他窗口操作 [[NSApplication sharedApplication] stopModal];//结束模态 //NSModalSession sessionCode = [[NSApplication sharedApplication] beginModalSessionForWindow:self.userLoginWinodwC.window]; //[[NSApplication sharedApplication] endModalSession:sessionCode];
-
4.进行判空处理——避免重复创建窗口!(继续上面代码逻辑)
-- 4-1.未进行判空处理——在'ViewController.swift'中:import Cocoa class ViewController: NSViewController ,NSWindowDelegate { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } var sub1WC: LeftWindowController? func createAndShowWindow() { //【二】直接使用‘NSNib’形式的NSWindowController,作为窗口控制器 sub1WC = LeftWindowController(windowNibName: "LeftWindowController") sub1WC? .showWindow(nil) print(sub1WC as Any) } @IBAction func leftBtnClick(_ sender: Any) { self .createAndShowWindow() } @IBAction func rightBtnClick(_ sender: Any) { } override var representedObject: Any? { didSet { // Update the view, if already loaded. } } }
效果:点击'Left'按钮创建新窗口B,多次点击则多次创建新窗口B(2/3/4/5……)——均是不同的对象!
注:会创建多个新窗口LeftWindowController对象
-- 4-2.进行了判空处理——在'ViewController.swift'中:import Cocoa class ViewController: NSViewController ,NSWindowDelegate { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } var sub1WC: LeftWindowController? func createAndShowWindow() { //【二】直接使用‘NSNib’形式的NSWindowController,作为窗口控制器 sub1WC = sub1WC != nil ? sub1WC : LeftWindowController(windowNibName: "LeftWindowController")//判空,避免重复创建 sub1WC? .showWindow(nil) print(sub1WC as Any) } @IBAction func leftBtnClick(_ sender: Any) { self .createAndShowWindow() } @IBAction func rightBtnClick(_ sender: Any) { } override var representedObject: Any? { didSet { // Update the view, if already loaded. } } }
效果:第一次点击'Left'按钮创建新窗口B,后续多次点击只展示该新窗口B——都是相同的对象!
注:不会创建多个新窗口LeftWindowController对象
- 5.自定义一个提示窗口!
参考:《macOS警告/提示视图 — NSAlert、自定义WindowController》
-
6.设置窗口的尺寸、位置——使用window的“
.setContentSize()
”方法和“.setFrameOrigin()
”方法~初始的默认尺寸:480x270(如下图,在"Main.storyboard"中查看)
Main.storyboard在'ViewController.swift'文件中:
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [self] in //设置window的尺寸、位置 self.view.window?.setContentSize(NSMakeSize(300, 300)) self.view.window?.setFrameOrigin(NSMakePoint(100, 100)) //self.view.window?.frameRect(forContentRect: NSMakeRect(100, 100, 300, 300))//❌❌ DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [self] in //设置window的尺寸、位置 self.view.window?.setContentSize(NSMakeSize(500, 500)) self.view.window?.setFrameOrigin(NSMakePoint(200, 200)) //self.view.window?.frameRect(forContentRect: NSMakeRect(200, 200, 500, 500))//❌❌ } } }
效果:启动App后,窗口的尺寸是默认的480x270!延时3s后尺寸变为300x300、起点坐标为(100, 100)!再延时3s后尺寸变为500x500、起点坐标为(200, 200)!
VIewController层的使用(NSVIewController、对应的view)
上面的情况是:在Window层中包含了自己对应的VIewController层,即一个窗口中一个NSVIewController的架构!
然后就可以在对应的self.view
上进行视图布局~
还有一种情况是:一个(Window层)窗口中,可以有多个NSVIewController~
直接使用系统的NSViewController,初始化后的实例来进行操作:
let testVC = NSViewController()//❌//Failed to set (contentViewController) user defined inspected property on (NSWindow): -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: NSViewController in bundle (null). testVC.view.wantsLayer = true testVC.view.layer?.backgroundColor = NSColor.red.cgColor
直接使用系统的NSViewController,会报错“
Failed to set (contentViewController) user defined inspected property on (NSWindow): -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: NSViewController in bundle (null).
”,并且不能使用该视图控制器!未勾选'Also create XIB file for user interface'
自定义一个(继承自NSViewController类)未使用‘NSNib’形式的视图控制器:(GYHNoXibViewController)再初始化后的实例来进行操作:
let testVC = GYHNoXibViewController()//❌//Failed to set (contentViewController) user defined inspected property on (NSWindow): -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: MacDealViewController.GYHNoXibViewController in bundle (null). testVC.view.wantsLayer = true testVC.view.layer?.backgroundColor = NSColor.red.cgColor
使用自定义但未使用‘NSNib’形式**的视图控制器,会报错“
Failed to set (contentViewController) user defined inspected property on (NSWindow): -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: MacDealViewController.GYHNoXibViewController in bundle (null).
”,并且不能使用该视图控制器!
这个跟上面Window层讨论的情况相似,必须要使用‘NSNib’形式的NSViewController类实例(如下含'xib'的):
使用‘NSNib’形式的NSViewController类实例~
自定义一个(继承自NSViewController类)使用‘NSNib’形式的视图控制器:(GYHHasXibViewController)
勾选'Also create XIB file for user interface'再初始化后的实例来进行操作:
let testVC = GYHHasXibViewController() testVC.view.wantsLayer = true testVC.view.layer?.backgroundColor = NSColor.red.cgColo
结论:使用自定义并且使用‘NSNib’形式的视图控制器,不会报错且可以使用该视图控制器!
操作代码如下:使用 自定义且使用了‘NSNib’形式的视图控制器—(GYHHasXibViewController)
import Cocoa
let Button_TAG = 1000
class ViewController: NSViewController {
var orderVC: NSViewController?
var settingVC: NSViewController?
var userVC: NSViewController?
var currentVC: NSViewController?//当前选中的VC
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//初始化视图控制器
self.orderVC = GYHHasXibViewController()
self.orderVC?.view.wantsLayer = true
self.orderVC?.view.layer?.backgroundColor = NSColor.red.cgColor //红
self.settingVC = GYHHasXibViewController()
self.settingVC?.view.wantsLayer = true
self.settingVC?.view.layer?.backgroundColor = NSColor.green.cgColor //绿
self.userVC = GYHHasXibViewController()
self.userVC?.view.wantsLayer = true
self.userVC?.view.layer?.backgroundColor = NSColor.blue.cgColor //蓝
//添加子视图控制器VC
self .addChild(self.orderVC!)
self .addChild(self.userVC!)
self .addChild(self.settingVC!)
self.currentVC = self.orderVC//当前选中的VC(初次)
self.view .addSubview(self.currentVC!.view)
let margin: CGFloat = 10.0
let btn_W: CGFloat = 100.0
let btn_H: CGFloat = 30.0
let arr = ["订单", "设置", "个人信息"]
for i in 0..<arr.count {
let btn = NSButton(frame: NSMakeRect(margin, margin + (margin + btn_H)*CGFloat(i), btn_W, btn_H))
btn.title = arr[i];
btn.tag = i + Button_TAG
self.view .addSubview(btn)
btn.target = self; btn.action = #selector(clickButton)
}
}
@objc func clickButton(btn: NSButton) {//点击各按钮
switch (btn.tag - Button_TAG) {
case 0: do {//"订单"
self .new_prensentToVC(toVc: self.orderVC as! GYHHasXibViewController)
}
case 1: do {//"设置"
self .new_prensentToVC(toVc: self.settingVC as! GYHHasXibViewController)
}
case 2: do {//"个人信息"
self .new_prensentToVC(toVc: self.userVC as! GYHHasXibViewController)
}
default:
break
}
}
func new_prensentToVC(toVc: GYHHasXibViewController) {
if (self.currentVC == toVc) {//当前选中的VC 就是 要跳转的VC
return
}
print("self.currentVC:\(self.currentVC as Any), tovc:\(toVc)")
self .transition(from: self.currentVC!, to: toVc, options: NSViewController.TransitionOptions()) {
self.currentVC = toVc//当前选中的VC
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
效果:a.选择相应"订单"/"设置"/"个人信息"按钮,就会转到相应的界面(红/绿/蓝)!b.如果‘当前选中的VC’就是‘要跳转的VC’,则不进行跳转操作(‘open func transition(from fromViewController: NSViewController, to toViewController: NSViewController, options: NSViewController.TransitionOptions = [], completionHandler completion: (() -> Void)? = nil)
’方法)!
在"Debug View hierarchy"中查看视图层次:
Tips:在
self.view
视图里面,可以使用一个子视图(作为容器视图)来放入 上述自定义的视图控制器!
(这点与iOS中代码操作类似~)self.view .addSubview(self.currentVC!.view)
换为
self.view .addSubview(self.currentVC!.view)//可注释、可不注释 //单独使用(容器)子视图,来存放各VC对应的view let containerV = NSView(frame: NSMakeRect(150, 10, 300, 220)) self.view .addSubview(containerV) containerV .addSubview(self.currentVC!.view)
效果:a.对应自定义的视图控制器放在了子视图(作为容器视图)里面了!b.选择相应"订单"/"设置"/"个人信息"按钮跳,会转到相应的界面(红/绿/蓝)!c.如果‘当前选中的VC’就是‘要跳转的VC’,则不进行跳转操作(‘open func transition(from fromViewController: NSViewController, to toViewController: NSViewController, options: NSViewController.TransitionOptions = [], completionHandler completion: (() -> Void)? = nil)
’方法)!在"Debug View hierarchy"中查看视图层次:
goyohol's essay