構建你今天想要用的東西就好,而不是你覺得人們在某種程度上會使用的東西。
— Paul Graham
首先,什么是導航控制器?比如 table views。導航控制器在 iOS應用是另一個自定義 UI部分。它為分層內容提供了一個向下的界面。看看內置照片應用,YouTube和記事本。他們都使用導航控制器來顯示分層內容。一般,你用一個視圖控制器的堆棧與導航控制器聯合起來為你的應用構建一個盡量簡潔的界面。盡管如此,這不意味著你必須一起使用它們。導航控制器可以和任何風格的視圖控制器一起工作。
Storyboards里的場景(Scenes)和延續(Segues)
直到現在,我們僅僅在 FoodPin應用的 Storyboard里布局了一個 table view 控制器。Storyboard允許你做得更多。你可以在 storyboard 里添加更多的視圖控制器來連接他們。所有這些工作能夠在沒有一行代碼的情況下完成。當用 storyboards 工作時,場景(scenes)和延續(segues)是你必須知道的兩項。在 storyboard 里,一個場景通常涉及到屏幕上內容(如一個視圖控制器)。segues位于2個場景之間,表示一個場景到另一個的切換。Push 和 Modal 是兩種常見類型的轉變。
Quick note:在 Xcode7里,介紹一個新的特性叫做 storyboard references,這個參考讓 storyboards 更易于管理和更模塊化。當你的工程變得大而復雜時,你可以把一個大的 storyboard分割成多個 storyboard 然后用 storyboard references連接他們。當你和你的團隊成員合作創建一個 storyboard 時這個特性是特別有用的。
創建導航控制器
我們將通過內嵌table view 控制器進入一個導航控制器來繼續在 Food Pin應用上工作。當一個用戶選擇任何餐廳時,這個應用導航到下個屏幕來顯示餐廳細節。
如果你已經關閉了 FoodPin 工程,現在是時候打開 Xcode 然后再次打開工程。選擇 Main.storyboard 來切換到界面構建器編輯。Xcode 提供一個內嵌特性來使它在一個導航控制器里很容易嵌入任何視圖控制器。選擇 table view 控制器然后點擊菜單里的”Editor”。選擇“Embed in”>“Navigation Controller”。
Xcode 自動在一個導航控制器嵌入控制器。我們設置導航欄的標題。選擇 table view 控制器的導航欄。在 Attributes inspector 下,把title 值改成 Food Pin。
現在運行應用看看它看起來如何。這個應用和以前一樣,但是添加了一個導航欄。
添加一個視圖控制器細節
很簡單,對嗎?僅僅點幾下,你已經給你的應用添加一個導航欄。不過這里漏掉了顯示餐廳細節的視圖控制器。當用戶選擇一個餐廳,應用傳遞給細節視圖控制器然后顯示餐廳細節。
在視圖構建器里,從對象控制器里拖出一個視圖控制器來創建細節視圖控制器。我們讓細節視圖越簡單越好。我們僅僅在細節視圖里顯示餐廳照片。從對象庫里拖曳一個圖像視圖到視圖控制器。調整它的大小來適應適合,然后添加為圖像視圖添加間距約束。為了確保圖片正確的縮放,進入 Attributes inspector 把 mode從 Scale to Fill 改成 Aspect Fill。
現在你在 storyboard 里配置了兩個視圖控制器。這里有個問題,你如何才能把他們連接在一起?在 storyboard 里,我們需要通過一個 segue 來連接他們。在音樂方面,一個 segue 是兩段音樂之間無縫的過渡。在 storyboard 里, 兩個場景之間的過渡叫做 segue。
當用戶按下單元格時,table view 控制器將傳遞到細節場景。所以我們將添加一個 segue 來連接 prototype cell 和細節場景。添加一個 segue 對象是很容易的。按住 control 鍵,點擊 prototype cell 然后拖到細節視圖控制器。
如果你覺得在 storyboard editor 里選擇 prototype cell 很難,打開文檔大綱視圖,把 prototype cell 拖到 view controller。當你松開按鈕時,一個彈出菜單會出現,給你來選擇 segue 的類型。選擇類型為“Show”,然后你會看到在 view controller 之間有一個接頭。
一旦選好了,Xcode 用一個 show segue自動連接 prototype cell 和細節視圖控制器。
在 iOS9里,它支持以下的 segue 類型:
- Show - 當使用 show 類型時,內容被推送到當前視圖控制器堆棧的頂上。一個返回按鈕將顯示在導航欄來返回原始視圖控制器。我們為導航控制器使用這個 segue 類型。
- Show detail - 類似于 show 類型,但是細節(或目標)視圖控制器里的內容取代當前視圖控制器堆棧的頂部。例如,如果你在 FoodPin 應用 里選擇show detail 而不是 show,那樣在細節視圖里將沒有導航欄和返回按鈕。
- Present modally - 程序化的顯示內容。使用時,細節視圖控制器將從底部開始動畫效果然后覆蓋整個 iphone屏幕。一個“present modally”segue 的好例子是內置日歷應用 的“add”特性。當你點擊應用里的+按鈕時,它會從底部引出一個“新事件”。
- Present as popover - 作為一個彈窗固定到現有的視圖來呈現內容。Popover 通常能在 iPad 應用中找到。下圖為你顯示了一個例子。從 iOS8開始,你也可以在 iPhone 應用上使用 popover segue。
Quick note:這些 segue 類型從 iOS8開始被棄用:Push,Modal,Popover,Replace。
現在,你可以準備運行應用。登陸時,選擇一個餐廳,應用應該會導航到細節視圖控制器。同時,細節視圖控制器僅僅顯示一個黑色的屏幕。好的消息是你已經創建了一個導航界面。
沒有寫一行代碼,你已經在你的應用里添加了一個導航控制器。但是,我猜在你的腦海里會有一些疑問:
- 你如何才能從 RestaurantTableViewController 傳遞餐廳信息到細節視圖控制器?
- 你如何才能在細節視圖控制器里顯示選擇的餐廳圖片。
我們待會將會一個個的解決它們。
在進入下一章之前,我們來做一點小的調整。當你運行應用程序選擇一個單元格時,它會導航到空白的屏幕,顯示一個我們曾經在第十章實現的動作表單。我們不再需要那個動作表單。之后我們將在細節視圖控制器里添加相同的功能。因此,從 RestaurantTableViewController.swift 里移除 tableView(_:didSelectRowAtIndexPath:) 方法。
Quick tip:有時你可能僅僅想要注釋掉一個代碼塊而不是刪除它們。Xcode 提供一個快捷鍵來注釋多行代碼。首先,選擇代碼塊然后按住 command+slash。Xcode 會自動用注釋指示器標記(comment indicators)代碼塊。想要移除注釋,再做一次同樣的程序。
為細節視圖控制器創建一個新的類
Okay,我們回到細節視圖控制器。我們的目標是在視圖控制器里為被選擇的餐廳更新圖片。視圖控制器現在默認與 UIViewController 類相連。事實是 UIViewController 類僅僅提供基本的視圖管理模式。這里面沒有變量用于儲存餐廳圖片。明顯的,我們必須擴展 UIViewController 來創建我們自己的類以便于我們能為圖片視圖添加新的變量。
在工程導航器里,右擊”FoodPin”文件夾然后選擇”New File…”。選擇”Cocoa Touch Class”作為類模板。把這個類命名為 RestaurantTableViewController 然后把它設置為 UIViewController 的一個子類。點擊”Next”然后在你的工程文件夾里保存文件。
和平常一樣,分配 DetailViewController 類到 storyboard 里的細節視圖控制器。在界面構建器里,選擇細節視圖控制器然后打開 Identity inspector。改變自定義類到 RestaurantTableViewController。
給自定義類添加變量
我們必須在自定義類里添加一堆東西:
- 創建一個變量叫做 restaurantImage — 當用戶在 table view 控制器里選擇一個餐廳時,應該有個辦法來傳遞圖片名給細節視圖。這個變量將被用于數據傳遞。
- 為圖片視圖創建一個叫做 restaurantImageView 的outlet — 我們需要一個參考來更新細節圖像控制器的圖像視圖,所以我們不得不創建一個 outlet。
Okay,給 RestaurantTableViewController 類添加下面的代碼。
@IBOutlet var restaurantImageView:UIImageView!
var restaurantImage = “”
接下來,在 restaurantImageView 變量和細節視圖控制器的圖片視圖之間建立聯系。回到 Main.storyboard。右擊文檔大綱里的Restaurant Detail View Controller對象。在彈出菜單里,連接 restaurantImageView outlet 和圖像視圖控制器。
既然你已經聯系了變量和故事板里的圖像視圖對象,但是仍然有一件事沒做。你還沒有讀取圖片。圖片視圖應該顯示被選擇的餐廳圖片。在RestaurantTableViewController 類的 viewDidLoad 方法里,添加一行代碼。你的方法應該看起來像這樣:
overrid func viewDidLoad() {
super.viewDidLoad()
//加載視圖后做任何額外的設置。
restaurantImageView.image = UIImage(named:restaurantImage)
}
當視圖加載到內存里時,viewDidLoad 方法被調用。在這個方法里,你能提供額外的自定義視圖。在上面,我們簡單的設置圖像視圖的圖像給被選擇餐廳的圖像。
試著編譯運行你的應用。在選擇一個餐廳之后這個細節視圖仍然是空白的。我們仍然搞錯了一件事。我們沒有從 table view控制器傳遞餐廳圖像到細節視圖控制器。這是為什么 restaurantImage 變量沒有被分配任何值。
用 segues 傳遞數據
接下來是本章的核心部分,關于用 segues 傳遞數據。一個 segue 管理視圖控制器之間的傳遞,包含了涉及到過渡的視圖控制器。當一個 segue 觸發時,在可視化過渡出現之前,storyboard 運行時通過調用 prepareForSegue 方法通知源視圖控制器(如R estaurantTableViewController).默認prepareForSegue 方法的實施沒做任何事。通過重寫方法,你可以傳遞任何相關數據到新的控制器,這個控制器在我們工程叫做 RestaurantDetailViewController。
segues 可以被很多來源觸發。隨著你的 storyboard 變得更復雜,在視圖控制器之間你有超過一個的 segue是很有可能的。因此,最好的實踐是給每個在 storyboard 里的 segue 一個唯一的標識符。這個標識符是一個字符串,用來區分 segue。為一個 segue 分配一個標識符,在 storyboard 編輯器里選擇這個 segue,然后進入 Attributes inspector。設置 identifier 值為 showRestaurantDetail。
隨著配置好 segue,插入以下在 RestaurantTableViewController.swift 里的代碼來重寫 prepareForSegue 方法的默認實施:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == “showRestaurantDetail” {
if let indexPath = tableView.indexPathForSelectedRow {
let destinationController = segue.destinationViewController as!
RestaurantDetailViewController
destinationController.restaurantImage = restaurantImages[indexPath.row]
}
}
}
第一行代碼被用來核查 segue 的標識符。這個代碼塊僅僅對 showRestaurantDetail segue 生效。在代碼塊里,我們首先通過訪問 tableView.indexPathForSelectedRow 來檢索選擇的行。indexPath 對象應該包含選擇的單元格。
一個 segue 對象同時包含源和目標視圖控制器來參與過渡。你使用segue.destinationViewController來檢索目標控制器。在這種情況下,目標控制器是 RestaurantDetailViewController對象。這就是為什么我們必須用 as! 操作符來向下轉換它。最終,我們傳遞選擇餐廳的圖片到目標控制器。