實現導航

在本章中,咱們將使用導航控制器並繼續建立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)。但這兩個按鈕沒有綁定動做,你點擊它們沒有任何反應。接下來咱們會配置這兩個按鈕的動做

 

使用自動佈局完成UI(Xcode7下可用)

這是一段時間以來,對於你原來創建的用戶界面,有不少事情發生了改變。在這一點上,你不用對你的佈局作出任何改變,因此自動佈局看起來很好用。
要作到這一點,你須要對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,而且不會添加任何新的菜譜

相關文章
相關標籤/搜索