Mac开发

macOS App层次结构、使用WindowController

2021-08-18  本文已影响0人  goyohol


首先要说明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层的特殊情况







注:不会创建多个新窗口LeftWindowController对象









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

上一篇下一篇

猜你喜欢

热点阅读