macOS App层次结构、使用WindowController层/VIewController层

2,615 阅读13分钟

首先要说明iOS使用的Cocoa Touch框架,而macOS使用Cocoa框架!相比之下macOS的App的层次结构稍微复杂一些~

创建macOS的App 创建macOS的App

咱先创建好macOS的App后,默认产生的'Main.storyboard'含有了三个层次:Application SceneWindow Controller SceneView Controller Scene
Mac App标准结层次构Application → (WindowController→Window) → (ViewController→View)

Mac App标准结层次构:Application → (WindowController→Window) → (ViewController→View) Mac App标准结层次构:Application → (WindowController→Window) → (ViewController→View)

Application Scene层次

Application Scene Application Scene

Window Controller Scene层次

Window Controller Scene Window Controller Scene

View Controller Scene层次

View Controller Scene View Controller Scene

Window/WindowControllerView/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的类 继承自NSViewController名字为MainViewController的类

通过'.xib'文件中控件IBOutletIBAction,完成如下代码的配置

通过'.xib'文件,完成代码的配置 通过'.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)

大概讲一下NSWindowNSWindowController基础属性~ 演示代码书写如下:

@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后操作)!

关于NSWindowNSWindowController基础属性更多详细信息,请参考
NSWindow:developer.apple.com/documentati…
NSWindowController:developer.apple.com/documentati…

窗口对象:www.macdev.io/ebook/windo…



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'文件中控件IBOutletIBAction,完成如下代码的配置

    完成代码的配置 完成代码的配置

    -- 使用如下代码创建并展示窗口控制器(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)实例:窗口控制器contentViewController属性是 自定义未使用NSNib’形式的WrongSub1ViewController类型!

    未勾选'Also create XIB file for user interface' 未勾选'Also create XIB file for user interface'

    @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类型的实例作为窗口控制器contentViewController——含'xib'的 使用如下代码创建并展示窗口控制器(NSWindowController)实例:窗口控制器contentViewController属性是 自定义使用NSNib’形式的Sub1ViewController类型!

    勾选'Also create XIB file for user interface' 勾选'Also create XIB file for user interface'

    @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类型的实例,作为窗口控制器!
    使用如下代码创建并展示窗口控制器(NSWindowController)实例:窗口控制器自定义使用NSNib’形式的**LeftWindowController类型**!

    勾选'Also create XIB file for user interface' 勾选'Also create XIB file for user interface'

    窗口控制器类型 — LeftWindowController:

    'LeftWindowController.swift'文件 'LeftWindowController.swift'文件

    'LeftWindowController.xib'文件 '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.不会报错!

    对于“//sub1WC = LeftWindowController2()//无反应 //未使用‘NSNib’形式的窗口控制器”代码,其所使用自定义的**LeftWindowController2类型的窗口控制器 没有使用**‘NSNib’形式!

    未勾选'Also create XIB file for user interface' 未勾选'Also create XIB file for user interface'

    更多属性的效果,通过'.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——developer.apple.com/documentati…


  • 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()//结束模态’的方法 直接调用‘NSApplication .shared .stopModal()//结束模态’的方法

    注:在'LeftWindowController.swift'文件中将模态结束后,(默认的'ViewController.swift'中)才会继续执行’DispatchQueue.main.asyncAfter‘延时!

    效果2:运行成功后默认的窗口A可以进行正常操作;点击'Left'按钮创建新窗口B后窗口A还可以正常操作,但延时2s后开始模态只有新窗口B可以进行操作(默认的窗口A不可进行操作了);点击新窗口B左上方关闭’按钮后在**windowWillClose 回调方法结束模态后默认的窗口A可以进行操作**了!

    代理方法windowWillClose中,调用‘NSApplication .shared .stopModal()//结束模态’的方法 代理方法windowWillClose中,调用‘NSApplication .shared .stopModal()//结束模态’的方法

    对应的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对象



  • 6.设置窗口的尺寸位置——使用window的“.setContentSize()”方法和“.setFrameOrigin()”方法~

    初始的默认尺寸:480x270(如下图,在"Main.storyboard"中查看)

    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).”,并且不能使用该视图控制器!

自定义一个(继承自NSViewController类)未使用NSNib形式的视图控制器:(GYHNoXibViewController)

未勾选'Also create XIB file for user interface' 未勾选'Also create XIB file for user interface'

再初始化后的实例来进行操作:

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' 勾选'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