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


首先要说明iOS使用的Cocoa Touch框架,而macOS使用Cocoa框架!相比之下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)

Application Scene层次

Application Scene

Window Controller Scene层次

Window 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的类

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

通过'.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:https://developer.apple.com/documentation/appkit/nswindow
NSWindowController:https://developer.apple.com/documentation/appkit/nswindowcontroller

窗口对象:http://www.macdev.io/ebook/window.html



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'
    @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'
    @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'

    窗口控制器类型 — 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.不会报错!

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

    未勾选'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——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可以进行操作了!

    代理方法`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

    在'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'

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容