Xcode所提供的默認模板包括一個WindowController,還有一個ViewController,在ViewController中還有一個View,我們的控件一般都寫在這個View中。而起始,storyboard把一個邏輯給簡化了,關於Window,WindowController,View和ViewController,這四個類可以說是相互依存的。
如果我們不使用storyboard,那麼程序就會去讀取AppDelegate中的代碼(如果是用默認模板的話,把storyboard刪除之後要記得在設置中把默認storyboard刪除)。我們應用程序顯示的第一個窗口就需要在此定義。由於Cocoa框架嚴格遵守着MVC模式,因此,要想在屏幕上顯示一個窗口,那麼一定就要擁有模型,視圖和對應的控制器。那麼,既然是要生成一個窗口,我們就需要一個NSWindow或其子類的實例。NSWindow有這樣一個初始化函數:
public convenience init(contentViewController: NSViewController)
這裏的意思是說,我們要一個窗口,那麼窗口裏究竟顯示什麼東西,是需要一個ViewController說了算的,所以我們還需要一個ViewController,而ViewController有這樣一個構造函數:public init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
既然有了視圖控制器,那一定是用來顯示視圖的,那視圖在哪裏呢?一般是用xib文件(編譯之後就成爲nib文件)來編輯的,所以調用這個方法就可以加載nib文件。當然,如果你的View是用代碼定義的,那在這裏給兩個參數傳空就可以了,然後操作NSViewController的一個屬性來改變它的視圖:
open var view: NSView
之後,有了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工程
然後建立工程:
刪除storyboard裏的視圖和控制器:
再刪除已經作廢的ViewController源文件:
之後,我們創建三個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
}
}
其實說來說去,這幾行代碼都是完全一樣的,只是用在了不同的地方而已,我們有三個窗口,所以就需要三套這樣體系的文件,當然也就需要三套用於加載的代碼。主窗口要一開始就顯示,所以寫在應用程序代理中,而兩個子窗口是點擊按鈕以後顯示,所以寫在的按鈕的實現文件中。
關於這四個類的簡單說明基本就到這裏,當然本實例只是爲了說明用法,所以代碼風格上來說並不規範,在實際開發的時候,我們還是應該對這些代碼進行更高層次的封裝,也要對相對應的初始化函數進行改寫,但是說到底實現的功能都是這些,本質上是不變的。