Swift之macOS開發中NSWindow, NSWindowController, NSView, NSViewController的關係

https://blog.csdn.net/fl2011sx/article/details/73252859swift

 

macOS使用的Cocoa框架,的確沒有iOS使用的Cocoa Touch那麼智能好用。有些地方邏輯很奇怪,還有一些看似很正常的功能它卻沒有提供,還須要自定義。這裏就有一個很頭疼的問題,關於這四個類的問題,他們之間究竟是什麼關係,若是擺脫了storyboard如何用代碼實現?今天就來簡單介紹一下。app

    Xcode所提供的默認模板包括一個WindowController,還有一個ViewController,在ViewController中還有一個View,咱們的控件通常都寫在這個View中。而起始,storyboard把一個邏輯給簡化了,關於Window,WindowController,View和ViewController,這四個類能夠說是相互依存的。框架

 

    若是咱們不使用storyboard,那麼程序就會去讀取AppDelegate中的代碼(若是是用默認模板的話,把storyboard刪除以後要記得在設置中把默認storyboard刪除)。咱們應用程序顯示的第一個窗口就須要在此定義。因爲Cocoa框架嚴格遵照着MVC模式,所以,要想在屏幕上顯示一個窗口,那麼必定就要擁有模型,視圖和對應的控制器。那麼,既然是要生成一個窗口,咱們就須要一個NSWindow或其子類的實例。NSWindow有這樣一個初始化函數:ide

 

public convenience init(contentViewController: NSViewController)函數

 

這裏的意思是說,咱們要一個窗口,那麼窗口裏究竟顯示什麼東西,是須要一個ViewController說了算的,因此咱們還須要一個ViewController,而ViewController有這樣一個構造函數:.net

public init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)設計

 

    既然有了視圖控制器,那必定是用來顯示視圖的,那視圖在哪裏呢?通常是用xib文件(編譯以後就成爲nib文件)來編輯的,因此調用這個方法就能夠加載nib文件。固然,若是你的View是用代碼定義的,那在這裏給兩個參數傳空就能夠了,而後操做NSViewController的一個屬性來改變它的視圖:代理

 

open var view: NSViewcode

 

    以後,有了window,咱們還得須要一個控制器來把這個窗口顯示在屏幕上(目前爲止全部的數據還都是內存數據而已,咱們還須要調用顯示方法),因此就用到了NSWIndowController,它提供了一個構造函數:對象

 

public init(window: NSWindow?)

 

    這樣就齊全了,咱們能夠看到,NSWindowController裏會包含一個它要控制的NSWindow,而NSWindow須要一個NSViewController來管理具體顯示的視圖,最後還須要一個具體的NSView。當咱們準備齊全這些之後,就能夠調用NSWindowController的一個方法,顯示窗口:

 

@IBAction open func showWindow(_ sender: Any?)

 

    關於這裏的sender,官方的解釋是動做的發起者,通常是應用程序代理,可是本人嘗試,其實填什麼都好像不影響結果,哪怕是nil,也能夠正常顯示。具體的含義還有待繼續摸索。

 

    還有就是關於storyboard的建議,其實在作macOS開發的時候,storyboard並很差用,不像在iOS開發時那樣駕輕就熟,因此仍是建議把視圖的設計用xib,而後關於控制器的部分用代碼來書寫。可是也並不建議直接把storyboard刪掉,由於用它來編輯狀態欄的下拉菜單仍是很是方便地,因此本人的作法是在storyboard中只留一個file menu,把其餘的視圖和控制器都刪除掉。固然這樣的話,在項目設置處的入口storyboard就必須還得是Main才行。

 

    接下來用一個具體的例子來講明上面的這一系列問題。咱們製做一個簡單的應用程序,它的主界面有兩個按鈕,當點擊第一個按鈕的時候建立一個新的窗口,當點擊第二個按鈕的時候也建立一個新的窗口,同時還關閉主窗口。

 

    分析上面的要求,咱們確定是須要3套內容,每一套裏都應該含有一個WIndowController,一個Window,一個ViewController和一個View。

 

    首先,建立一個Cocoa工程

 

 

 

 

 

    以後,咱們建立三個ViewController以及xib文件,Command+N,選擇Cocoa Class,輸入mainViewController,勾選xib文件:

 

    而後用一樣的方法生成sub1ViewController和sub2ViewController: 

    對於sub1和sub2,咱們只是可以區分就好,因此在xib裏隨便拖個控件什麼的,能認清楚就行,而對於mainViewController.xib,咱們須要兩個按鈕,而且還要關聯到mainViewController.swift中點擊方法,這裏再也不贅述,完成以後的效果以下:

 

 

 

    咱們來重點編輯這兩個函數,這裏有一點須要注意的是,WindowController必須持久存在,不然會形成窗口閃退的現象,因此,咱們要確保WindowController時刻存在一個引用,這樣它纔不會被ARC回收掉,那麼最好的辦法就是讓他成爲一個成員變量,這樣就能夠保持引用。而至於其餘的對象,在WindowController內部會保持鏈接,因此只要WindowController在,它們就必定在,因此咱們用局部變量來做爲一個「接力手」就能夠了。

 

    好比說咱們要在btn1Click(_:)方法中顯示視圖1,那麼首先要有一個ViewController來加載對應的xib文件,而後要建立一個窗口關聯它,再把它給到WindowController中就能夠了,具體代碼以下:

 

//

 

//  mainViewController.swift

 

 

import Cocoa

 

 

class mainViewController: NSViewController {

 

 

    override func viewDidLoad() {

 

        super.viewDidLoad()

 

        // Do view setup here.

 

    }

 

    open var windowController: NSWindowController?

 

    var sub1WindowController: NSWindowController?

 

    @IBAction func btn1Click(_ sender: NSButton) {

 

        // 建立視圖控制器,加載xib文件

 

        let sub1ViewController = NSViewController(nibName: "sub1ViewController", bundle: Bundle.main)

 

        // 建立窗口,關聯控制器

 

        let sub1Window = sub1ViewController != nil ? NSWindow(contentViewController: sub1ViewController!) : nil

 

        // 初始化窗口控制器

 

        sub1WindowController = NSWindowController(window: sub1Window)

 

        // 顯示窗口

 

        sub1WindowController?.showWindow(nil)

 

    }

 

    var sub2WindowController: NSWindowController?

 

    @IBAction func btn2Click(_ sender: NSButton) {

 

        // 同上

 

        let sub2ViewController = NSViewController(nibName: "sub2ViewController", bundle: Bundle.main)

 

        let sub2Window = sub2ViewController != nil ? NSWindow(contentViewController: sub2ViewController!) : nil

 

        sub2WindowController = NSWindowController(window: sub2Window)

 

        sub2WindowController?.showWindow(nil)

 

        // 加上一句關閉當前窗口

 

        windowController?.close()

 

    }

 

    

 

}

 

    須要說明的一點是,因爲關閉窗口是WindowController管的,因此要想在ViewController裏操做的話,就須要傳入進來才行,因此這裏的成員windowController就是如此。

 

    雖然咱們這個邏輯實現了,可是如今打開應用程序仍是沒有窗口的,由於咱們主窗口尚未顯示出來,因此咱們還須要在應用程序加載完畢後加載主窗口,因此還要在AppDelegate中對mainView實現一個相同的功能:

 

//

 

//  AppDelegate.swift

 

 

import Cocoa

 

 

@NSApplicationMain

 

class AppDelegate: NSObject, NSApplicationDelegate {

 

 

    var mainWindowController: NSWindowController?

 

 

    func applicationDidFinishLaunching(_ aNotification: Notification) {

 

        // Insert code here to initialize your application

 

        let mainViewController_ = mainViewController(nibName: "mainViewController", bundle: Bundle.main)

 

        let mainWindow = mainViewController_ != nil ? NSWindow(contentViewController: mainViewController_!) : nil

 

        mainWindowController = NSWindowController(window: mainWindow)

 

        mainViewController_?.windowController = mainWindowController

 

        mainWindowController?.showWindow(nil)

 

    }

 

 

    func applicationWillTerminate(_ aNotification: Notification) {

 

        // Insert code here to tear down your application

 

    }

 

 

 

}

--------------------- 

做者:fl2011sx 

來源:CSDN 

原文:https://blog.csdn.net/fl2011sx/article/details/73252859 

相關文章
相關標籤/搜索