在本章中,咱們將使用導航控制器並繼續建立FoodTracker app的導航流程。在課程結束後,你將有一個導航策略和交互流程。當你完成時,你的app看起來以下所示:android
學習目標
在課程結束時,你將學會:
1.在storyboard中的導航控制器內嵌入一個已經存在的視圖控制器swift
2.在兩個視圖控制器之間建立橋樑app
3.在storyboard的Attributes inspector內編輯一個segue的屬性ide
4.經過使用prepareForSegue(_:sender:)來在視圖控制器之間傳遞數據佈局
5.執行一個unwind segue(用於實現向後導航的一個segue類型)學習
6.使用stack view來建立健壯,靈活的佈局(Xcode 7.0)動畫
添加一個segue到向前導航ui
數據顯示如預期同樣,是時候提供一個方法來從meal list場景到meal場景的導航了。場景之間的轉換經過調用segues(相似android的intent)spa
在建立一個segue之間,你須要配置你的場景。首先你把table view controller放入一個導航控制器的內部。導航控制器經過向前和向後來管理一系列view controller的轉換。經過一個特定的導航控制器來管理一個view controllers集,這被稱爲導航堆棧,第一個添加到棧中的會成爲root view controller,它永遠不會從導航堆棧彈出。設計
添加導航控制器到你的meal list場景
1.打開你的storyboard,Main.storyboard
2.選擇table view controller(你也能夠經過 scene dock來選擇)
3.在table view controller被選中的狀況下,選擇Editor > Embed In > Navigation Controller
Xcode會添加一個新的導航控制器到你的storyboard中,設置storyboard的入口點,並在新的導航控制器和已存在的table view控制器之間建立一個關係
在畫布中,會有一個鏈接到控制器icon,它是root view controller的關係。table view controller是導航控制器的root view controller。storyboard的入口點設置爲導航控制器,是由於導航控制器是一個現有的table view controller的容器。你可能注意到table view頂部有一個欄了。這就是導航欄。每個在導航棧中得到一個導航欄的控制器,能包含向前,向後導航。接下來,你須要添加一個按鈕到這個導航欄來過渡到meal場景。
檢查站:運行你的app。在你table view的上方,應該能夠看到額外的空間。這是導航控制器提供的導航欄。導航欄會擴展它的背景到狀態欄的頂部,因此狀態欄不會和你的內容重疊了
爲場景配置導航欄
如今,你將添加一個標題和一個按鈕到導航欄。導航欄從當前顯示的導航controller中,得到他們的標題。導航控制器自己沒有標題,它包裹的內容纔有標題。你使用meal list的導航item設置標題,而不是在導航欄直接設置它。
在meal list配置導航欄
1.雙擊meal list場景中的導航欄(點擊中間)
會出現一個光標,讓你輸入文本
2.輸入Your Meals而後按下Return來保存
3.打開Object library
4.找到 Bar Button Item對象
5.拖動Bar Button Item對象到導航欄的最右邊
一個Item的按鈕會出如今,你鬆開的地方
6.選擇 bar button item,打開 Attributes inspector
7.在 Attributes inspector,在標籤Identifer旁,選擇Add
按鈕會變成一個(+)
檢查點,執行你的APP,導航欄會顯示一個標題和一個(+)按鈕。如今這個按鈕不會作任何事,接下來咱們會修復它
你想要經過點擊(+)按鈕跳轉到meal場景,因此咱們會經過點擊按鈕觸發一個segue來跳轉到那個場景
配置(+)按鈕
1.在畫布上,選擇(+)按鈕
2.按住Control鍵拖動按鈕到meal場景中
一個Action Segue的快捷菜單出現,鬆開的地方
Action Segue菜單容許你選擇segue的類型
4.這裏咱們選擇Show
Xcode設置Action Segue並配置meal場景用於顯示,如今Interface Builder中的界面以下:
檢查站:執行你的APP,你如今能夠點擊(+)按鈕並能夠從meal list場景導航到meal場景了。由於你使用導航控制器來顯示一個segue,那向後導航已經自動幫你處理好了,會自動出現一個back按鈕在你的meal場景中。這意味着你能點擊back按鈕回到meal list場景
推送風格導航用於顯示segue。可是在增長item時,這可能並非你想要的。推送導航設計於鑽取界面,不管用戶選擇什麼,你應該提供更多信息。增長一個item,另外一方面是一個模式的操做,用戶執行一個動做,這是完整的,自成體系的,而後從場景返回到主導航。對於這個類型的場景展現,有一個合適的方法叫modal segue。(須要用戶在展現的控制器中執行一個操做,才能返回到主流程)
若是要刪除已存在的segue並建立一個新的,在Attributes inspector中簡單的改變segue的風格便可。如大多數在storyboard可選的元素同樣,你能使用Attributes inspector來編輯一個segue的屬性
改變segue的風格
1.在meal list場景和meal場景之間選中segue(那個小箭頭)
2.在Attributes inspector中,找到Seque標籤,下拉選擇Present Modally
3.在Attributes inspector中,找到Identifier標籤,輸入AddItem,而後Return
後面咱們會須要這個標示符來識別segue
一個modal的視圖控制器不被添加到導航棧,所以它不會有一個導航欄。然而,你想要保持導航欄來提供給用戶視覺連續性。當展現modal時,爲了給meal場景一個導航欄,它會嵌入在本身的導航控制器中
添加一個導航控制器到meal場景
1.選中 meal scene
2.選中meal場景的狀況下,選擇Editor > Embed In > Navigation Controller
和之前同樣,Xcode添加一個導航控制器並顯示一個導航欄在meal場景的頂部,接下來,配置這個導航欄,咱們添加兩個按鈕Cancel,Save。和一個標題。你會用來這兩個按鈕來執行一些動做。
在meal場景中配置導航欄
1.雙擊meal場景中的導航欄(點中間),出現一個光標,讓你輸入文本
2.輸入New Meal而後按Return
3.在Object library中拖動Bar Button Item對象到導航欄最左邊
4.在Attributes inspector中,找到Identifier標籤,選擇Cancel。
按鈕的文本變成了Cancel
5.在Object library中拖動Bar Button Item對象到導航欄右邊
6.在Attributes inspector中,找到Identifier標籤,選擇Save
按鈕的文本變成了Save
檢查站:執行的app,點擊(+)按鈕。而後會出現meal場景,但meal場景中不會有back導航。你會在上方看見兩個按鈕(Cancel和Save)。但這兩個按鈕沒有綁定動做,你點擊它們沒有任何反應。接下來咱們會配置這兩個按鈕的動做
這是一段時間以來,對於你原來創建的用戶界面,有不少事情發生了改變。在這一點上,你不用對你的佈局作出任何改變,因此自動佈局看起來很好用。
要作到這一點,你須要對stack view作一些簡單的調整。
更新stack view的佈局
1.在meal場景中,選中stack view
2.在畫布的底部右邊,打開Resolve Auto Layout Issues菜單
3.選擇Update Constraints
元素的位置仍是沒變,但 stack view如今被固定於導航欄上,而不是View的頂部邊緣。如今UI看起來以下:
檢查站:執行的app。一切看起來都和之前同樣
在Meal List中保存新的Meals
接下來咱們要實現一個添加新菜譜的功能。當用戶輸入菜譜名稱,評級和照片時,點擊Save按鈕,你想要MealViewController配置一個Meal對象,而後返回適當的信息到
MealTableViewController的菜譜列表場景中來顯示。首先咱們添加一個Meal屬性到MealViewController中
添加一個Meal屬性到MealViewController中
1.打開MealViewController.swift
2.找到MealViewController.swift,在ratingControl的outlet下,添加如下屬性
/* This value is either passed by `MealListTableViewController` in `prepareForSegue(_:sender:)` or constructed as part of adding a new meal. */ var meal = Meal?()
這個屬性是可選的,由於它有可能爲nil的狀況
你只須要在點擊Save按鈕時,關心配置和傳遞Meal。因此咱們須要添加一個Save按鈕的outlet到MealViewController.swift中
鏈接Save按鈕到MealViewController代碼中
1.打開你的storyboard
2.打開assistant editor
3.在storyboard中,選中Save按鈕
4.按住Control鍵拖動Save按鈕到MealViewController.swift中的
ratingControl屬性下
5.在彈出的對話框中,Name標籤旁,輸入saveButton,而後點擊Connect
建立一個Unwind Segue
如今的任務是當用戶點擊Save按鈕時,傳遞Meal對象到MealTableViewController。當用戶點擊Cancel按鈕時,則取消。
要作到這點,你將使用一個unwind segue。一個unwind segue,能夠經過一個或多個segues向後返回到一個已存在的view controller實例中。你使用unwind segues來實現反向導航。
每當一個segue被觸發,它提供一個讓你添加代碼並執行的地方。這個方法叫prepareForSegue(_:sender:),它可讓你存儲數據並作一些必要的清理工做。你能夠在MealViewController
中實現這個方法來作到這點
在MealViewController中實現prepareForSegue(_:sender:)方法
1.返回到standard editor
2.打開MealViewController.swift
3.在MealViewController.swift上方,添加註釋
// MARK: Navigation
4.在註釋下方,添加以下代碼
// This method lets you configure a view controller before it's presented. override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { }
5.在prepareForSegue(_:sender:)方法中,添加if語句
if saveButton === sender { }
(===)操做符用來檢查對象的引用是否相同,即
saveButton和sender是不是同一個對象。若是是,if語句會執行
6.在if語句中,添加以下代碼
let name = nameTextField.text ?? "" let photo = photoImageView.image let rating = ratingControl.rating
這段代碼從當前文本框,選中的image,和評級數據三個方面建立了常量
注意,在name
這行使用了空值合併運算符(??)。這個運算符對於可選變量有值時,返回一個值,若是可選變量爲nil時,則返回默認值。這裏咱們經過nameTextField.text來返回一個值,他可能爲空,若是用戶沒有在文本框中輸入內容,那麼就爲nil,則返回空串("")
7.接着在if語句中,添加以下代碼
// Set the meal to be passed to MealListTableViewController after the unwind segue. meal = Meal(name: name, photo: photo, rating: rating)
這段代碼用來在segue執行前使用適當的值來配置meal屬性
如今完整的prepareForSegue(_:sender:)方法看起來以下:
// This method lets you configure a view controller before it's presented. override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if saveButton === sender { let name = nameTextField.text ?? "" let photo = photoImageView.image let rating = ratingControl.rating // Set the meal to be passed to MealListTableViewController after the unwind segue. meal = Meal(name: name, photo: photo, rating: rating) } }
接下來咱們建立的unwind segue會添加一個動做方法到目標視圖控制器(就是segue將要去的視圖控制器)。這個方法必須標記爲IBAction屬性來獲取一個segue(UIStoryboardSegue
)做爲參數。由於你想要unwind segue返回到meal list場景,你須要添加一個這種格式的動做方法到 MealTableViewController.swift中。
在這個方法中,你將寫邏輯來來添加新的菜譜到meal list數據中並會在meal list場景下的table view內添加新的一行
添加一個動做方法到MealTableViewController
1.打開MealTableViewController.swift
2.在MealTableViewController.swift中,(})以前,添加以下代碼:
@IBAction func unwindToMealList(sender: UIStoryboardSegue) {
}
3.在unwindToMealList(_:)動做方法內,添加如下if語句
if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal{ }
if語句中會發生不少事情。
代碼使用可選類型強制轉換操做符(as?),試圖子類強轉到源view controller的segue到MealViewController類型。你須要子類強轉,由於sender.sourceViewController是UIViewController類型,但你須要使用MealViewController工做。
這個操做符返回一個可選值,若是子類強轉不可行,那麼它將會是nil。若是子類強轉成功,代碼會分配view controller到局部常量sourceViewController,並檢查是否
sourceViewController中的
meal屬性爲nil。若是meal屬性非nil,代碼分配屬性值到局部常量meal並執行if語句。
4.在if語句中,添加如下代碼
// Add a new meal. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0)
代碼會計算table view中新插入的cell的顯示位置,並存儲它在局部常量newIndexPath中
5.在if語句中,添加如下代碼
meals.append(meal)
添加新的菜譜到已存在的meals列表中(數據模型)
6.在if語句中,添加如下代碼:
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom)
會有個動畫添加新的行(cell)到table view中,它會包含新的菜譜信息。.Bottom動畫選項會顯示從底部滑動插入
你稍後將完成一個更高級的方法實現,但如今unwindToMealList(_:)動做方法看起來以下:
@IBAction func unwindToMealList(sender: UIStoryboardSegue) { if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal { // Add a new meal. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0) meals.append(meal) tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom) } }
如今你須要建立一個實際的unwind segue來觸發這個動做方法
鏈接Save按鈕到unwindToMealList動做方法
1.打開你的storyboard
2.在畫布中,按住Control鍵拖動Save按鈕到meal場景的Exit items上
當你鬆開時,會出現一個提示
3.從快捷菜單中選擇unwindToMealList:
如今當用戶點擊Save按鈕時,導航返回到meal list場景,在此期間,unwindToMealList(_:)動做方法會被調用
檢查站:執行你的app。如今當你點擊(+)按鈕時,建立一個新的菜譜,而後點擊保存,你將會看見新的菜譜出如今你的meal list中
若是你沒有看到在快捷菜單中unwindToMealList方法,確保該方法具備正確的簽名:@IBAction func unwindToMealList(sender: UIStoryboardSegue)
當用戶沒有輸入一個Item Name時,禁用保存
若是沒有name時,用戶點擊save按鈕會發生什麼?由於在MealDetailTableViewController中的meal屬性是可選的,因此若是沒有name,那麼你的初始化程序會失敗,Meal對象不會建立,也不會添加到meal list場景中。但你能夠在軟鍵盤消失前,檢測用戶是否指定了一個有效的name,若是用戶意外的沒有添加meal的name,那麼咱們禁用Save按鈕
當沒有name時,禁用Save按鈕
1.在MealViewController.swift找到 // MARK: UITextFieldDelegate
2.而後添加另外一個UITextFieldDelegate協議內的方法
func textFieldDidBeginEditing(textField: UITextField) { // Disable the Save button while editing. saveButton.enabled = false }
當編輯開始時,或當軟鍵盤顯示時,textFieldDidBeginEditing會被調用。而後咱們經過代碼來禁用Save按鈕
3.在textFieldDidBeginEditing(_:)方法下方添加另外一個方法
func checkValidMealName() { // Disable the Save button if the text field is empty. let text = nameTextField.text ?? "" saveButton.enabled = !text.isEmpty }
這個幫助方法用來檢查當文本框爲空時,禁用Save按鈕
4.找到textFieldDidEndEditing(_:)方法,添加以下代碼:
checkValidMealName()
navigationItem.title = textField.text
第一行是檢查文本框是否爲空,來啓用或禁用Save按鈕。第二行是設置場景的標題爲文本框中的文本
6.找到viewDidLoad()方法,而後添加以下代碼:
// Enable the Save button only if the text field has a valid Meal name. checkValidMealName()
首先,載入界面後,確保Save按鈕是被禁用的
完整的 viewDidLoad()方法以下
override func viewDidLoad() { super.viewDidLoad() // Handle the text field’s user input through delegate callbacks. nameTextField.delegate = self // Enable the Save button only if the text field has a valid Meal name. checkValidMealName() }
完整的textFieldDidEndEditing()方法以下
func textFieldDidEndEditing(textField: UITextField) { checkValidMealName() navigationItem.title = textField.text }
檢查站:執行的APP。如今當你點擊(+)按鈕時,Save按鈕首先會被禁用,直到你輸入一個有效的meal name並關閉軟鍵盤後Save按鈕可用
取消新菜譜的添加
用戶可能決定取消添加一個新的菜譜,並返回到meal list場景中。對於這點,咱們須要實現Cancel按鈕的行爲
建立和實現取消動做方法
1.打開你的storyboard
2.打開assistant editor
3.在storyboard中,選中Cancel按鈕
4.按住Control鍵拖動Cancel按鈕到 MealViewController.swift
代碼中// MARK: Navigation註釋的下方
5.在彈出的對話框中,Connection旁選擇Action
6.Name標籤旁,輸入cancel
7.Type標籤旁,選擇UIBarButtonItem
8.點擊Connect,出現如下代碼:
@IBAction func cancel(sender: UIBarButtonItem) {
}
9.在cancel(_:)動做方法中,添加如下代碼:
dismissViewControllerAnimated(true, completion: nil)
這行代碼是讓meal場景消息,沒有存儲任何信息
你完整的cancel(_:)動做方法以下:
@IBAction func cancel(sender: UIBarButtonItem) { dismissViewControllerAnimated(true, completion: nil) }
檢查站:執行你的APP,如今當你點擊(+)按鈕後,點擊Cancel按鈕,你將導航回到meal list,而且不會添加任何新的菜譜