OS X View Controller 指南

編譯自:https://www.raywenderlich.com...html

(本文原文十分值得一讀,然而我翻譯的略渣,有些直譯不出來的,是我根據理解編的。務必各類指出錯誤。基於此,暫時請勿轉載)swift

蘋果公司的開發框架一直圍繞着 Modal-View-Controller,提供了多種控制器對象用於管理 UI,以便於咱們的代碼,易於理解,便於維護。windows

視圖控制器是 OS X 程序中一個極其重要的概念,它是 Modal 層和 View 層之間的橋樑。數組

本文探討的內容較多,包括使用視圖控制器構建你的應用程序、視圖控制器重要的回調事件以及與窗口控制器的比較。瀏覽器

開始以前,你須要裝好最新版的 OS X 和 Xcode。你要開發的可不是一個鬧着玩的應用程序!推薦你先讀讀 Garbriel Miro’s 的窗口和窗口視圖指南,但這不是必須的。安全

視圖控制器介紹

視圖控制器用於管理視圖以及視圖的子視圖。 OS X 中,繼承自 NSViewControllerapp

OS X 10.5 引入了視圖控制器,那時它不是 responder chain 的一部分。這有什麼影響呢?舉例來講,視圖控制器上面有一個按鈕,然而它卻不能處理按鈕的事件,很尷尬吧。OS X 10.10 改進了視圖控制器,從那之後,它成爲一個構建複雜界面時十分有用的工具。框架

有了視圖控制器,能夠很好的規劃你的窗口。視圖控制器專一與視圖有關的交互和事件,像調整窗口大小、關閉窗口這種與窗口有關的事件只放在窗口控制器中處理。因而代碼就變的很乾爽。ide

使用視圖控制器額外的好處是易於複用。好比你有一個文件瀏覽器,左邊部分的文件瀏覽視圖是經過視圖控制器來實現的,這時你剛好須要一個相似的視圖,你能很容易的複用它。剩下的時間和精力陪女友逛逛街也好啊。工具

視圖控制器和窗口控制器

那麼,啥時候使用窗口控制器,啥時候使用視圖控制器呢?

若是你期待的視圖控制器工做行爲是 UIViewController 那種,那麼 OS X 10.10 Yosemite 之前的 NSViewController 會讓你失望。

Apple 基於 MVC 設計了 iOS 的UIViewController視圖控制器,管理視圖的生命週期、視圖操做、響應控件事件等都包含其中,10.10 以後的NSViewController加入了這些特性。在視圖控制器裏面完成你的視圖,以及響應和視圖有關的事件,而後設置窗口控制器的主 viewController、大小、標題等成爲了標準流程。

通過這些改進,構建複雜交互的時候,能夠良好的解耦,在多個視圖控制器中完成需求,而後整合到一塊兒。

(譯註:這段實在是翻譯不出來,是根據意思寫出來的,請對照原文使用)

視圖控制器實踐

本教程將經過開發一個名爲RWStore的應用程序,用於選擇查看不一樣raywenderlich.com store 的書籍來實踐。

打開 Xcode 選擇建立新工程,而後從模板中選擇OS X\ Application\ Cocoa Application,而後點擊Next

將這個項目命名爲RWStore。使用 Swift 做爲開發語言,同時勾選上 Use Storyboards。勾選掉單元測試和 UI 測試的選項,你暫時還不須要他們。點擊 Next保存你的項目。

kkkk

下載項目須要的資源文件。這個壓縮包包含所需的圖片以及書籍商品所須要的數據,他們保存在Products.plist文件。此外你還能看到一個名爲Product.swift的源碼文件。這個文件包含Product類,它解析了 Product 對象的結構。接下來把他們添加到RWStore項目中。

從項目導航中選擇選擇Assets.xcassets,將剛剛下載的圖片資源拖進去。

而後將Products.plistProduct.swift拖到項目導航中。確保勾選了Copy items if needed

這時編譯運行應用程序。

能夠看到空白的主窗口,但不要驚慌,正常運行就是好的開始。

建立用戶界面

打開Main.storyboard,選擇View Controller Scene,拖拽一個pop-up 按鈕到 view 中,以後會用到。

經過 AutoLayout 來設置 它的位置。選擇剛剛拖拽的 Pop-up 按鈕,點擊下方的Pin按鈕。在彈出來的窗口中,將其LeadingTrailingTop的約束值都設置爲Use Standard Value

接着完成界面。拖拽一個 container view 放到剛剛添加的 pop-up按鈕下方。

container view是一個佔位視圖,其餘視圖或者視圖控制器能夠經過它顯示。

選擇剛剛添加的container view,點擊下方的Pin阿牛。添加top、bottom、trailing、leading四個約束,將其設置爲0。而後點擊Add 4 constrains按鈕。

選擇你 storyboard 中的視圖控制器,而後點擊Pin按鈕右側的Resolve Auto Layout Issues按鈕,選擇All Views in Controller/Update Frames。這時你的界面看起來是這個樣子的:

如今在代碼中響應你的視圖行爲。打開Assistant Editor(快捷鍵[alt] + [cmd] + [enter])確認 ViewCotroller.swift 已經被打開。拖拽pop-up按鈕到ViewController.swift中,添加行爲鏈接,命名爲valueChanged,類型是NSPopUpButton

剛剛建立的 Container 視圖,自帶了一個以 embed 方式鏈接的視圖控制器,咱們須要自定義,選擇它並刪除。

Tab View Controllers

如今,咱們將添加一個視圖控制器,用於顯示 Product 信息:咱們選擇Tab View Controller。它的視圖包含幾個選項卡,以及視圖控制器。每個選項卡對應一個視圖控制器。選項卡切換的時候,對應的視圖控制器被切換顯示。

選擇Tab View Controller,拖拽到Storyboard中。

將剛剛添加的Tab View ControllerContainer View使用embed方式鏈接起來:

雙擊左側的選項卡,將標題改成Overview;雙擊右側的選項卡,將標題改成Details

編譯並運行應用程序。

能夠看到,剛剛咱們設計的視圖控制器已經能正常顯示了,點擊選項卡也能正常切換對應的視圖控制器。由於咱們尚未爲其添加內容,因此兩個視圖控制器如今都仍是空白。

Over View Controller

接下來須要建立這個。

File\New\File,選擇OS X\Source\Cocoa Class,點擊Next。類名爲OverviewController,繼承自NSViewController,不要勾選Also Create XIB for user interface,點擊Next建立完成並保存。

回到Main.storyboard,選擇Overview Scene。點擊視圖上藍色的按鈕,選擇類對象,在右側的Identity Inspector的 class 輸入框中輸入 OverviewController。

拖拽三個 labelOverviewController 的視圖的左上方,一個接一個的排列。添加一個image view在視圖的右上角。

提示:默認狀況下,image view 沒有邊框,給它設置個圖片,這樣好找。選擇Attributes Inspector,選擇gamesImage字段。這個圖片是剛剛資源文件裏的,應該能夠看到效果啦。

選擇最最上面的標籤。Attributes Inspector裏面將字體設置爲System Bold,字號設置爲 19。

這時候的視圖看起來是這樣的:

好!讓咱們使用 AutoLayout 來調整一下佈局。

選擇 image view,點擊下面的Pin按鈕。給其添加約束:top 和 trailing設置爲 standard valuewidthheight的值設置爲 180。

選擇最上面的標籤,仍是添加約束,將top、bottom、leading 和 trailing設置爲 standard value

選擇挨着的下面的標籤,添加約束:將trailing 和 leading設置爲standard value

選擇最下面的一個標籤,添加約束:將leading、trailing、bottom設置爲standard value。點擊top約束,確認image view是選擇狀態,而後選擇Use the standard value

提示:若是你不能看到 image view 在選擇菜單,請確保 label 足夠寬,且置於 image view 的下方。

點擊下方區域的Resolve Auto Layout,選擇All Views in Controller/Update Frames,你的視圖看起來應該是這樣的:

界面工做到如今能夠告一段落了,編譯運行,如今他長這樣:

點擊標籤按按鈕這時能看到視圖控制器之間的差異了。咱們一行代碼沒寫就獲得了一個不錯的界面。

添加代碼

先來把界面上的控件鏈接到你的代碼中。

打開Assistant Editor,選擇OverviewViewController.swift。按着Ctrl而後拖拽到OverviewController.swift中,命名爲titleLabel。類型爲NSTextField

重複上面的操做,將剩餘的控件都鏈接到代碼中:

  1. 中間的標籤命名爲:priceable

  2. 下面的標籤命名爲:descriptionLabel

  3. image view 命名爲:productImageView

和大多數 UI 控件同樣,標籤和 image view 都有子視圖,選擇的時候仔細一下,別選錯了,好比NSImageView選成了NSImageCellNSTextField選成了NSTextFieldCell

點擊OverviewController,加上下面的代碼:

//1
let numberformatter = NSNumberFormatter()
//2    
var selectedProduct: Product? {
  didSet {
    updateUI()
  }
}

這段代碼:

  1. number formatter是一個NSNumberFormatter,用於正確格式化價格。

  2. selectedProduct對應擋圈選擇的商品。每當值發生變化,didSet裏面的代碼被執行,而後調用updateUI()更新界面。

如今給OverviewController添加updateUI方法。

private func updateUI() {
  //1
  if viewLoaded {
    //2
    if let product = selectedProduct {
      productImageView.image = product.image
      titleLabel.stringValue = product.title
      priceLabel.stringValue = numberformatter.stringFromNumber(product.price)!
      descriptionLabel.stringValue = product.descriptionText
    }
  }
}
  1. 經過 viewLoaded 屬性判斷 NSViewController 是否已經加載,若是已經加載完畢,就能夠安全的訪問與視圖有關的屬性了。

  2. 解包selectedProduct肯定是否已經選擇了產品。而後顯示正確的值。

這個方法如今已經會在產品變換的時候調用,還須要在視圖加載完畢的時候調用。

視圖控制器生命週期

從視圖控制器具有響應視圖事件能力開始,它就爲視圖生命的各個階段提供了各類回調事件。好比視圖從 storyboard 被加載,或者顯示在屏幕上這種都屬於被 Hook 的事件範圍。全部這些機遇事件的方法被統稱爲view controller life cycle

視圖控制器生命週期能夠被劃分紅三個主要部分:建立、運轉、終止。每個部分都提供了可重載的方法知足你的須要。

建立

  1. viewDidLoad()當視圖被首次完整加載的時候調用,一些只執行一次的初始化工做適合在這個時候進行,如建立數值格式化對象,註冊通知,某些只須要調用一次的 API 等。

  2. viewWillAppear() 每當視圖將要被顯示的時候會被調用。好比咱們剛剛選擇 Overview 標籤,每次切換它都會被調用。當數據發生變化,這是個更新到界面的好時候。

  3. viewDidAppear()每當視圖顯示在屏幕上的時候,這個方法會被調用。這時適合作一些動畫。

運轉

視圖控制器被建立以後,一些與用戶交互的事件就該登場了:

  1. updateViewConstraints()當佈局每次被改變都會被調用,好比窗口大小變化。

  2. viewWillLayout()是佈局將要發生的時候進行調用。若是你須要調整你的約束,能夠在這時進行。

  3. viewDidLayout()當佈局完成以後被調用。

當重載這三個方法的時候,在其中你必須調用他們的super

終止

終止與建立對應:

  1. viewWillDisappear() 當視圖將要消失的時候調用。在viewDidAppear()開始的動畫這時能夠結束了。

  2. viewDidDisappear()視圖消失以後這個方法被調用。一切你不須要的東西均可以在這時被幹掉。好比已經無效的timer神馬的。

生命週期實踐

有關視圖控制器生命週期重要的事情都已經告訴你了,如今進行一個小測試。

問題:你想把用戶選擇的產品的時候,讓OverviewController的視圖顯示正確的產品詳情。該在啥時候去執行更新視圖的代碼?

打開OverviewController.swift,添加下面的代碼:

override func viewWillAppear() {
  updateUI()
}

重載了viewWillAppear,當用戶看到視圖以前,它會被正確更新。

數值格式化對象當前使用的是默認值,爲了更好的展現,最好把它配置成貨幣格式。viewDidLoad()是作這事兒的好地方。

OverviewControllerviewDidLoad()方法添加下面的代碼:

numberformatter.numberStyle = .CurrencyStyle

用戶在主界面選擇不一樣的商品,當事件發生,咱們須要通知OverviewController。在ViewController類中作這件事很合適,由於用戶操做的彈出按鈕就在這上面。打開ViewController.swift,添加下面的代碼:

private var products = [Product]()
var selectedProduct: Product!

products 是用來保存全部商品信息的數組。selectedProduct指向當前彈出按鈕所選擇的商品。

找到viewDidLoad(),添加下面的代碼:

if let filePath = NSBundle.mainBundle().pathForResource("Products", ofType: "plist") {
  products = Product.productsList(filePath)
}

加載本教程資源中包含全部商品信息的 plist,賦值給products屬性。接下來用這個數組初始化彈出按鈕。

打開Main.storyboard,選擇View Controller Scene,切換到Assistant Editor。確保ViewController.swift 被選擇,而後拖拽到ViewController.swift做爲一個 outlet,命名爲productsButton。確認類型爲NSopUpButton

返回ViewController.swift,找到viewDidLoad 添加下面的代碼:

//1
productsButton.removeAllItems()
//2
for product in products {
  productsButton.addItemWithTitle(product.title)
}
//3
selectedProduct = products[0]
productsButton.selectItemAtIndex(0)

這段代碼作了一些微小的工做:

  1. 刪除彈出按鈕中全部的數據。

  2. 遍歷商品數組,將全部商品的標題添加到彈出按鈕。

  3. 選擇數組中第一個商品。

最後,咱們還須要在彈出按鈕選擇條目發生變化時作出響應,找到valueChanged(_:)添加下面的代碼:

if let bookTitle = sender.selectedItem?.title,
  let index = products.indexOf({$0.title == bookTitle}) {
  selectedProduct = products[index]     
}

這段代嘗試根據彈出按鈕的標題在商品列表中查找對應的元素,而後把selectedProduct指向正確的商品對象。

如今是時候來完成選擇商品發生變化,通知OverViewController的功能了。先在ViewController添加一個OverViewController的引用:

private var overviewViewController: OverviewController!

當 ViewController 以嵌入的形式被加載的時候,prepareForSegue(_:, sender:)方法會被觸發,咱們能夠在這個時候獲得overViewController 的實例:

override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
  //1
  let tabViewController = segue.destinationController as! NSTabViewController
  //2
  for controller in tabViewController.childViewControllers {
    //3
    if controller is OverviewController {
      overviewViewController = controller as! OverviewController
      overviewViewController.selectedProduct = selectedProduct
    } else {
      //More later
    }      
  }
}
  1. 獲得標籤視圖控制器的引用。

  2. 遍歷子視圖控制器。

  3. 找到OverviewController,的實例,而後設置它的selectedProduct屬性。

找到valueChanged(_:)方法,在裏面的if let塊中添加代碼。

overviewViewController.selectedProduct = selectedProduct

編譯運行,當選擇不一樣的商品時候,能夠看到界面已經能正常更新了。

產品詳情視圖控制器

咱們來建立產品詳情的視圖控制器。

選擇File\New\File...,選擇OS X\Source\Cocoa Class,點擊Next。類名爲DetailViewController,繼承自NSViewController,不要勾選Also Create XIB for user interface。點擊Next保存。

打開Main.storyboard,選擇Details Scene。在Identity Inspector中將class改成DetailViewController

添加一個image view到詳情視圖。選中它點擊Pin按鈕建立約束。weightheight設置爲180top約束設置爲standard value

點擊Align按鈕,給視圖添加一個居中約束:Horizontally in the Container

在剛剛添加的圖像視圖下方添加一個標籤控件,設置字體bold,字號19。點擊Pin按鈕,添加約束:topleadingtrailing,值爲standard value

在剛剛設定的標籤下方再添加一個標籤。點擊Pin添加約束:topleadingtrailing。值爲standard value

拖拽一個NSBox在標籤下方。給它添加約束:topleadingtrailingbottom,值爲standard value

打開Attributes Inspector,設置字體爲bold,字號14。將title改成Who is this Book For?

NSBox 用來組織一組相關聯的 UI 元素很好用。並且有了標題看起來更明確。

拖拽一個標籤控價在NSBox裏面,選擇這個標籤控件,點擊Pin按鈕,添加topleadingtrailingbottom,所有設置爲standard value

而後更新你的界面,看起來是這樣的:

激活Assistant Editor,打開DetailsViewController.swift。添加四個IBOutlet,命名爲:

  1. productImageView for the NSImageView.

  2. titleLabel for the label with the bold font.

  3. descriptionLabel for the label below.

  4. audienceLabel for the label in the NSBox.

DetailviewController添加如下代碼:

// 1
var selectedProduct: Product? {
  didSet {
    updateUI()
  }
}
// 2
override func viewWillAppear() {
  updateUI()
}
// 3
private func updateUI() {
  if viewLoaded {
    if let product = selectedProduct {
      productImageView.image = product.image
      titleLabel.stringValue = product.title
      descriptionLabel.stringValue = product.descriptionText
      audienceLabel.stringValue = product.audience
    }
  }
}

這些代碼和Overview視圖控制器裏面的代碼很相似,你應該已經很熟悉了:

  1. 定義表示當前選中商品的selectedProduct屬性,當選擇其餘商品的時候更新視圖。

  2. 每次視圖被顯示的時候,強制刷新(好比切換選項卡會就會觸發)。

  3. 將商品信息顯示在視圖上的圖像視圖和標籤控件中(經過 updateUI)

當被選擇的商品發生變化,你須要從主視圖控制器通知商品詳情視圖控制器。打開ViewController.swift,給商品詳情視圖控制器添加一個引用。在overviewViewController屬性下面增長下面的代碼:

private var detailViewController: DetailViewController!

而後找到valueChanged(_:)添加:

detailViewController.selectedProduct = selectedProduct

如今改變彈出按鈕的選項,詳情頁會被通知到。

最後一點改變是在prepareForSegue(_:, sender:)。找到註釋//More later,替換成下面的代碼:

detailViewController = controller as! DetailViewController
detailViewController.selectedProduct = selectedProduct

當商品詳情被嵌入的時候,當前選擇的商品信息會正常加載。

你的應用程序已經完成!

最後的一點有的沒的

你能從這裏下載完整的項目。

在本教程中,你學習瞭如下內容:

  • 什麼是視圖控制器和其與窗口控制器的區別

  • 建立一個自定義的試圖控制

  • 鏈接控件到你的視圖控制器

  • 操做視圖控制器

  • 視圖控制器的生命週期以及回調事件

若是想看看視圖控制器裏面都有啥,請移步官方文檔:https://developer.apple.com/l...

另外仍是推薦看一眼 tutorial on windows and window controllers

視圖控制器十分強大,並且在 OS X 應用程序開發中,它是十分有用的組件,涵蓋了許多值得學習的內容,加油!本文給開了個好頭,立刻去開發你想要的東東吧。

歡迎在下方留言進行討論。

相關文章
相關標籤/搜索