iOS Programming - 第五章 視圖控制器
視圖控制器是 UIViewController 的子類的一個(gè)實(shí)例。 視圖控制器管理著視圖層級(jí)。它負(fù)責(zé)創(chuàng)建組成視圖層級(jí)的視圖對(duì)象并在視圖層級(jí)中處理跟視圖對(duì)象關(guān)聯(lián)的事件。
視圖控制器中的 View
作為 UIViewController 的子類, 所有的視圖控制器都繼承了一個(gè)重要的屬性:
var view: UIView!
這個(gè)屬性指向一個(gè) UIView 實(shí)例, 這個(gè) view 是視圖控制器的視圖層級(jí)中的根視圖。當(dāng)視圖控制器的 view 被作為 window 的子視圖添加時(shí), 該視圖控制器的整個(gè)視圖層級(jí)就被添加上了。


視圖控制器的 view 直到它需要出現(xiàn)在屏幕上時(shí)才被創(chuàng)建。這種優(yōu)化叫做懶加載(lazy loading), 這能減少內(nèi)存使用并提升性能。
視圖控制器有兩種方式創(chuàng)建它的視圖層級(jí):
- 編寫(xiě)程序, 通過(guò)重寫(xiě) UIViewController 的 loadView 方法
- 在 Interface Builder 中, 通過(guò)使用諸如 storyboard 的界面文件
在第三章中你已經(jīng)使用了這兩種方法, 在第六章中你將使用 loadView() 創(chuàng)建程序上的視圖。
設(shè)置初始的視圖控制器
每個(gè) storyboard 都可以有很多視圖控制器, 但是每個(gè) storyboard 文件只有一個(gè)初始視圖控制器(initial view controller)。 初始視圖控制器是 storyboard 的入口點(diǎn)。你將在畫(huà)布中添加并配置另外一個(gè)視圖控制器并把該控制器設(shè)置為 storyboard 的初始視圖控制器。
打開(kāi) Main.storyboard, 從對(duì)象庫(kù)中, 拖拽一個(gè) ViewController 到畫(huà)布中, 如果你用完了可用的空間, 你可以按住 Ctrl 并單擊背景視圖來(lái)選擇不同的縮放比例。

用這個(gè)視圖控制器來(lái)顯示 MKMapView。先選中這個(gè) View Controller 的 view, 而不是 View Controller 自身! — 并按下 Delete 鍵來(lái)從畫(huà)布中刪除這個(gè) view。 然后從對(duì)象庫(kù)中拖拽一個(gè) Map Kit View 到這個(gè)視圖控制器中以把它設(shè)置為該視圖控制器的 view。

選中 View Controller 并打開(kāi)它的屬性檢查器。勾選 Initial View Controller , 則該視圖控制器的前面會(huì)出現(xiàn)一個(gè)灰色的箭頭。
MKMapView 是一個(gè)當(dāng)前未被加載到程序中的框架??蚣苁且粋€(gè)共享代碼庫(kù), 里面包含諸如界面文件和圖片等關(guān)聯(lián)的資源。
現(xiàn)在你需要導(dǎo)入 MapKit 框架以加載 MKMapView。使用 import 關(guān)鍵字但不使用包含任何使用該框架的代碼來(lái)導(dǎo)入 MapKit 會(huì)導(dǎo)致編譯器把它優(yōu)化掉 — 即使你在 storyboard 中使用 map view。
相反, 你需要手動(dòng)地把 MapKit 鏈接到 app 中。
打開(kāi)工程導(dǎo)航, 點(diǎn)擊工程名。在設(shè)置中打開(kāi) General 標(biāo)簽, 滾動(dòng)到最底部找到 Linked Frameworks and Libraries。 點(diǎn)擊 + 號(hào)并搜索 MapKit.framework, 選中這個(gè)框架并點(diǎn)擊 Add。

UITabBarController
UITabBarController 保存了一個(gè)視圖控制器的數(shù)組。UITabBarController 也在屏幕底部維護(hù)了一個(gè) tab bar, 數(shù)組中每個(gè)視圖控制器都帶有一個(gè) tab bar。觸摸標(biāo)簽(tab)會(huì)呈現(xiàn)跟該標(biāo)簽(tab)相關(guān)聯(lián)的視圖控制器。
打開(kāi) Main.stroyboard 并選擇 View Controller。 從 Editor 菜單中, 選擇 Embed In -> Tab Bar Controller。 這會(huì)把 View Controller 添加到 Tab Bar Controller 的視圖控制器數(shù)組中去。
按住 Control 鍵從 Tab Bar Controller 拖拽到 Conversion View Controller 上。在彈出的 Relationship Segue 一欄, 選擇 view controllers。

構(gòu)建并運(yùn)行該程序。此時(shí)標(biāo)簽上僅僅顯示的文字是默認(rèn)的 "Item"。在下一節(jié), 你可以更新 tab bar items 以使 tabs 更具描述性。
UITabBarController 自身是一個(gè) UIViewController 的子類。 UITabBarController 的 view 是一個(gè)含有兩個(gè)子視圖的 UIView: 即 tab bar 和 所選擇的視圖控制器的 view。

Tab bar items
tab bar 上的每個(gè)標(biāo)簽(tab)能展示一個(gè)標(biāo)題(title)和圖片(image), 為了這個(gè)目的, 每個(gè)視圖控制器維護(hù)了一個(gè) tabBarItem 屬性。當(dāng)視圖控制器被包含在 UITabBarController 中時(shí), 該視圖控制器的 tab bar item 會(huì)出現(xiàn)在 tab bar 中。

往 Assets.xcassets 中拖入素材。
tab bar item 屬性既能通過(guò) storyboard 也能通過(guò)編程來(lái)設(shè)置。
在 storyboard 中, 定位到 View Controller。 注意, 當(dāng)視圖控制器將被呈現(xiàn)在 tab bar controller 時(shí), 帶有 tab bar item 的 tab bar 被添加到界面中。這在布局你的界面時(shí)會(huì)很有用。
選擇這個(gè) tab bar item 并打開(kāi)它的屬性檢查器。 在 Bar Item 欄, 把 Title 設(shè)置為 "Map" 并從 Image 菜單中選擇 MapIcon。你也可以在畫(huà)布中雙擊文本來(lái)更改 tab bar item 的文本。

你還可以拖拽 tab bar item 以改變它們呈現(xiàn)的位置。
Loaded and Appearing Views
當(dāng)程序啟動(dòng)時(shí), tab bar controller 默認(rèn)會(huì)加載它的數(shù)組中的第一個(gè)視圖控制器的 view, 即 ConversionViewController。 這意味著 MapViewController 的 view 直到用戶點(diǎn)擊它的時(shí)候才被加載。
你可以自己測(cè)試這種懶加載行為。當(dāng)視圖控制器加載完它的視圖后, 會(huì)調(diào)用 viewDidLoad() 方法, 你可以重寫(xiě)該方法以打印信息到控制臺(tái)中。
你將為這兩個(gè)視圖控制器寫(xiě)代碼。 然而, 展示 map 的視圖控制器當(dāng)前并沒(méi)有代碼與之關(guān)聯(lián), 因?yàn)樗械呐渲枚际鞘褂?storyboard。你需要?jiǎng)?chuàng)建一個(gè)視圖控制器的子類并把它關(guān)聯(lián)到界面中。
創(chuàng)建一個(gè)名為 MapViewController 的 UIViewController 子類:
import UIKit
class MapViewController: UIViewController {
}
打開(kāi) storyboard 并選中該 map 的視圖控制器。打開(kāi)它的身份檢查器并把它的 Class 更改為 MapViewController。
現(xiàn)在你把 MapViewController 類和畫(huà)布中的視圖控制器關(guān)聯(lián)了起來(lái), 你可以在 ConversionViewController 和 MapViewController 中添加代碼了。
在 ConversionViewController.swift 中重寫(xiě) viewDidLoad() 方法,
override func viewDidLoad() {
// Always call the super implementation of viewDidLoad
super.viewDidLoad()
print("ConversionViewController loaded its view.")
}
在 MapViewController.swift 中重寫(xiě)同一個(gè)方法:
override func viewDidLoad() {
// Always call the super implementation of viewDidLoad
super.viewDidLoad()
print("MapViewController loaded its view.")
}
程序啟動(dòng)時(shí)就打印 ConversionViewController loaded its view, 當(dāng)點(diǎn)擊 Map Tab 時(shí)才打印 MapViewController loaded its view。并且都只會(huì)打印一次。 如果想執(zhí)行多次某個(gè)事件, 應(yīng)重寫(xiě) viewWillAppear 或 viewWillDisappear。
訪問(wèn)子視圖
通常, 在界面出現(xiàn)在用戶面前之前, 你需要在 Interface Builder 中定義的子視圖中做某些額外的初始化或配置。 所以你從哪里訪問(wèn)子視圖? 有兩種主要的觀點(diǎn), 取決于你需要干什么。一個(gè)地方是在 viewDidLoad() 方法中, 該方法在視圖控制器的界面文件被加載之后調(diào)用, 這時(shí)視圖控制器的所有 outlets 將會(huì)引用合適的對(duì)象。另外一個(gè)地方是在 viewWillAppear(_:) 中。這個(gè)方法在視圖控制器的 view 被添加到 window 上之前被調(diào)用。
你應(yīng)該選擇哪個(gè)方法? 在程序運(yùn)行期間, 如果配置只需要執(zhí)行一次, 就重寫(xiě) viewDidLoad() 。 如果需要在每次視圖控制器的 view 出現(xiàn)在屏幕之前執(zhí)行配置, 那么重寫(xiě) viewWillAppear(_:) 方法。
跟視圖控制器和它們的視圖交互
讓我們看看在視圖控制器和它的視圖的生命周期中所調(diào)用的方法。
- init(coder: ) 是從 storyboard 創(chuàng)建的 UIViewController 實(shí)例的初始化函數(shù)。
當(dāng)從 storyboard 創(chuàng)建視圖控制器實(shí)例時(shí), 會(huì)調(diào)用一次它的 init(coder:) 方法。在第 15 章中你會(huì)學(xué)到更多關(guān)于該方法的東西。
- init(nibName:bundle: ) 是 UIViewController 的指定初始化構(gòu)造函數(shù)。
當(dāng)不使用 storyboard 創(chuàng)建視圖控制器實(shí)例時(shí), 會(huì)調(diào)用一次它的 init(nibName:bundle: ) 方法。注意在某些 app 中, 你可以創(chuàng)建幾個(gè)同樣的視圖控制器類的實(shí)例。每創(chuàng)建一個(gè)視圖控制器就調(diào)用一次該方法。
- 重寫(xiě) loadView() 以編程方式創(chuàng)建視圖控制器的 view。
- 重寫(xiě) viewDidLoad 以配置通過(guò)加載界面文件創(chuàng)建的視圖。該方法在視圖控制器的 view 被創(chuàng)建之后調(diào)用。
- 重寫(xiě) viewWillAppear(_:) 以配置通過(guò)加載界面文件創(chuàng)建的視圖
該方法和 viewDidAppear(_:) 會(huì)在每次視圖控制器在移動(dòng)到屏幕上時(shí)被調(diào)用。viewWillDisappear(_:) 和 viewDidDisappear(_:) 會(huì)在每次視圖控制器移出屏幕時(shí)調(diào)用。