在本章中,你將關注添加行爲。它容許用戶編輯或刪除菜譜swift
學習目標數組
在課程結束,你將學會app
1.push和modal導航的區別ide
2.根據它們的展現風格來dismiss視圖控制器學習
3.理解子類強轉(downcasting)動畫
4.利用可選綁定檢查複雜條件this
5.使用segue標示符來肯定哪一個segue會發生spa
容許編輯已存在的Meals3d
當前,FoodTracker app給用戶提供添加新菜譜到列表的功能,接下來,你想要給用戶提供編輯功能。code
你容許用戶點擊一個meal cell來拉起一個meal場景,咱們已經預製了一些菜譜。用戶能夠使之改變並點擊Save按鈕保存,能夠用更新信息並覆蓋先前meal list中的記錄
配置table view cell
1.返回到standard editor
2.打開storyboard
3.在畫布上,選擇table view cell
4.按住Control鍵,拖動table view cell到meal場景
一個標題爲Selection Segue的快捷菜單出如今咱們鬆開的位置
5.選擇快捷菜單中,咱們選擇show
6.在meal list和meal場景之間向下拖動導航控制器,直到你能看到一個新的segue
若是你想縮小它們,你能夠使用Command-Minus (-
)
7.在畫布中,選中新添加的segue
8.在Attributes inspector中,找到Identifier標籤,在旁邊輸入ShowDetail,而後按下Return
當這個sugue被觸發時,它會爲meal場景push視圖控制器到,和meal list場景同樣相同的導航棧中。
檢查站:執行你的APP,在meal list場景下,你能夠點擊一個cell來導航到meal場景,但場景內容爲空白。當你點擊一個已存在的cell時,你想要編輯這個已存在的meal,而不是建立一個新的
你如今有兩個segues去往相同的場景,因此你須要一個方法來識別它們,用戶是想要添加仍是編輯。
回憶一下prepareForSegue(_:sender:)方法,它會在任意segue執行前調用。你能夠使用這個方法來識別哪一個segue在發生,並在meal場景中顯示適當的信息。你能夠基於你早起分配給它們的標示符來區別sugues:AddItem (modal segue) 和ShowDetail (show segue)
標識哪一個segue正在發生
1.打開MealTableViewController.swift
2.在MealTableViewController.swift中,找到並取消prepareForSegue(_:sender:)方法中的註釋。你作完後,模版方法應該以下所示:
// MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. }
由於MealTableViewController是UITableViewControlle的子類,模版實現伴隨prepareForSegue(_:sender:)結構
3.刪除兩行註釋,並替換爲if else語句
if segue.identifier == "ShowDetail" { } else if segue.identifier == "AddItem" { }
上面的代碼用於比較segue的標示符
4.在第一個if語句中,添加以下代碼:
let mealDetailViewController = segue.destinationViewController as! MealViewController
代碼試圖子類強轉目標segue的視圖控制器問爲MealViewController,使用強制類型轉換操做符(as!)。你這個操做符有一個感嘆號標記而不是問號,正如你所看到的,目前爲止的類型轉換操做。意思是這個操做執行一個強制的類型轉換。若是轉換成功,局部常量constant
mealDetailViewController會分配一個做爲MealViewController類型
segue.destinationViewController的
。若是強轉失敗,APP將會在運行時崩潰。若是你絕對確定強轉會成功,你能夠使用感嘆號(!),若是失敗的話,app會發生錯誤而且崩潰,若是你不願定,你能夠使用問號(as?)
5.在if語句中,添加以下代碼:
// Get the cell that generated this segue. if let selectedMealCell = sender as? MealTableViewCell { }
上面的代碼是試圖子類強轉sender到一個MealCell。若是轉換陳功,局部常量selectedMealCell會分配一個做爲MealTableViewCell類型的sender,而且if語句會執行。若是轉換失敗,表達式等於nil,if語句不執行
6.在if語句內部,添加以下代碼:
let indexPath = tableView.indexPathForCell(selectedMealCell)! let selectedMeal = meals[indexPath.row] mealDetailViewController.meal = selectedMeal
上面的代碼獲取對應選擇cell的Meal數據對象。而後分配Meal對象到目標視圖控制器的meal屬性中,這裏的目標視圖控制器是MealViewController。(當它載入時,你配置了MealViewController用來顯示
)meal中的信息
7.在else if語句中,添加print語句
print("Adding new meal.")
雖然不須要在這個裏面作任何事,但若是萬一你是添加新菜譜,而不是編輯的話,這是一個有用的提示
完整的prepareForSegue(_:sender:)方法看起來以下:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "ShowDetail" { let mealDetailViewController = segue.destinationViewController as! MealViewController // Get the cell that generated this segue. if let selectedMealCell = sender as? MealTableViewCell { let indexPath = tableView.indexPathForCell(selectedMealCell)! let selectedMeal = meals[indexPath.row] mealDetailViewController.meal = selectedMeal } } else if segue.identifier == "AddItem" { print("Adding new meal.") } }
如今你實現了邏輯,你須要在MealViewController.swift中作
一些工做,確保UI正確更新。當一個MealViewController(菜譜場景)實例被建立時,它的view會從meal屬性內填充數據。回憶一下這個設置工做合適的地方是在viewDidLoad()方法中
更新viewDidLoad()的方法實現
1.打開MealViewController.swift
2.在MealViewController.swift中找到viewDidLoad()方法
override func viewDidLoad() { super.viewDidLoad() // Handle the text field’s user input via delegate callbacks. nameTextField.delegate = self // Enable the Save button only if the text field has a valid Meal name. checkValidMealName() }
3. 在nameTextField.delegate = self這行下方,添加以下代碼:
// Set up views if editing an existing Meal. if let meal = meal { navigationItem.title = meal.name nameTextField.text = meal.name photoImageView.image = meal.photo ratingControl.rating = meal.rating }
上面的代碼是根據meal屬性來設置MealViewController中的顯示數據,前提是meal屬性不爲nil,這隻會發生在一個已存在的meal被編輯時。
完整的 viewDidLoad()方法以下所示:
override func viewDidLoad() { super.viewDidLoad() // Handle the text field’s user input via delegate callbacks. nameTextField.delegate = self // Set up views if editing an existing Meal. if let meal = meal { navigationItem.title = meal.name nameTextField.text = meal.name photoImageView.image = meal.photo ratingControl.rating = meal.rating } // Enable the Save button only if the text field has a valid Meal name. checkValidMealName() }
檢查站:執行你的app。你將能點擊cell導航到meal場景,並看見meal預製的數據。但若是你點擊Save,不是覆蓋已存在的meal,它仍是會添加一個新的meal。接下來咱們會修改這個地方
爲了覆蓋已存在的meal,你須要更新unwindToMealList(_:)動做方法來處理兩個不一樣的狀況,第一種狀況你須要添加一個新的mea,第二種你需求替換已存在的meal。回憶一下,這個方法只會在用戶點擊Save按鈕時調用,因此咱們不須要考慮Cancel按鈕
更新unwindToMealList(_:)方法的實現,能夠添加或替換meals
1.打開MealTableViewController.swift
2.在MealTableViewController.swift中,找到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) } }
3.在if語句內部的開始處,添加以下代碼
if let selectedIndexPath = tableView.indexPathForSelectedRow { }
這行代碼是檢查是否有一行被選中。若是是,表示用戶點擊了cell來編輯一個meal。換句話說,if語句會執行已經存在的meal。
4.在if語句中添加以下代碼
// Update an existing meal. meals[selectedIndexPath.row] = meal tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
上面的代碼是更新meals中合適的條目並存儲到meal數組中。第二行是重載適當的行來顯示改變後的數據
5.在if語句後,添加else語句
else { // Add a new meal. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0) meals.append(meal) tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom) }
當沒有選中行時,else語句會執行,意識是用戶點擊的(+)按鈕來跳轉到meal場景。換句話說,else語句執行一個新菜譜添加的狀況。
完整的unwindToMealList(_:)動做方法看起來以下:
@IBAction func unwindToMealList(sender: UIStoryboardSegue) { if let sourceViewController = sender.sourceViewController as? MealViewController, meal = sourceViewController.meal { if let selectedIndexPath = tableView.indexPathForSelectedRow { // Update an existing meal. meals[selectedIndexPath.row] = meal tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None) } else { // Add a new meal. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0) meals.append(meal) tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom) } } }
檢查站:執行你的app,你應該能點擊一個cell來導航到meal場景,若是你點擊Save,會改變的覆蓋的已存在的meal到meal list場景中
取消編輯已存在的Meal
有時候用戶可能決定放棄編輯這個meal,而後想要返回到meal list中,並非保存任何改變。對於這點,咱們能夠更新Cancel按鈕的行爲並適當的dismiss這個場景
被取消的類型取決於你展現的類型。你須要實現一個檢查,以肯定當用戶點擊Cancel按鈕時,當前場景是如何展現的。若是是modal展現的(+ 按鈕),它將經過dismissViewControllerAnimated(_:completion:)來dismiss。若是經過push導航展現的(cell),它將經過導航控制器來dismiss。
改變取消動做的實現
1.打開 MealViewController.swift
2.在MealViewController.swift,找到cancel(_:)動做方法
@IBAction func cancel(sender: UIBarButtonItem) { dismissViewControllerAnimated(true, completion: nil) }
這個實現僅用於(+)按鈕時的dismissViewControllerAnimated,由於那個時候咱們值須要考慮Add的狀況
3.在cancel(_:)動做方法中的第一行,插入以下代碼
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways. let isPresentingInAddMealMode = presentingViewController is UINavigationController
這裏建立了一個布爾值,用來表示視圖控制器是不是UINavigationController類型。由於meal場景是內嵌於本身的導航控制器中。
4.接下來把之前的callsdismissViewControllerAnimated
代碼,替換到if語句內
if isPresentingInAddMealMode { dismissViewControllerAnimated(true, completion: nil) }
而在此以前,調用dismissviewcontrolleranimated方法發生在cancel(_:)任意調用時,而如今它只發生ispresentinginaddmode爲真時。
5.在if語句以前,接着添加else語句
else { navigationController!.popViewControllerAnimated(true) }
else表示push導航展現的狀況,咱們能夠使用popViewControllerAnimated()方法以動畫的方式來從當前meal場景內離開導航棧
完整的cancel(_:)動做方法以下
@IBAction func cancel(sender: UIBarButtonItem) { // Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways. let isPresentingInAddMealMode = presentingViewController is UINavigationController if isPresentingInAddMealMode { dismissViewControllerAnimated(true, completion: nil) } else { navigationController!.popViewControllerAnimated(true) } }
檢查站:執行你的app。如今當你點擊(+)按鈕時,而後點擊Cancel你將向後導航回meal list,並不會添加任何新的菜譜
支持刪除Meals
接下來,咱們想要在meal list能夠刪除meal的功能。你須要一個方法來讓用戶經過在table view中以編輯模式來刪除這些cells。你能夠經過在table view導航欄中添加一個編輯按鈕來完成這個
添加編輯按鈕到table view
1.打開MealTableViewController.swift
2.在MealTableViewController.swift中,找到viewDidLoad()方法
override func viewDidLoad() { super.viewDidLoad() // Load the sample data. loadSampleMeals() }
3.在super.viewDidLoad()這行代碼的下方,添加以下代碼
// Use the edit button item provided by the table view controller. navigationItem.leftBarButtonItem = editButtonItem()
這會建立一個指定bar button item的類型,用於編輯行爲。它會添加這個按鈕在meal list場景導航欄的左邊
完整的viewDidLoad()方法,看起來以下
override func viewDidLoad() { super.viewDidLoad() // Use the edit button item provided by the table view controller. navigationItem.leftBarButtonItem = editButtonItem() // Load the sample data. loadSampleMeals() }
檢查站:執行你的app。如今有一個Edit按鈕出如今table view導航欄的左邊,若是你點擊這個按鈕,table view會進入編輯模式,但你如今還不能刪除它,由於你沒有實現這個功能
爲了執行任何種類的編輯,你須要實現它的委託方法tableView(_:commitEditingStyle:forRowAtIndexPath:)。當在編輯模式時,這個委託方法負責管理table的行數
刪除一個meal
1.在MealTableViewController.swift,找到tableView(_:commitEditingStyle:forRowAtIndexPath:)
,並移除註釋。而後模版方法以下
// Override to support editing the table view. override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { // Delete the row from the data source tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) } else if editingStyle == .Insert { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view } }
2.在// Delete the row from the data source註釋的下方,添加代碼
meals.removeAtIndex(indexPath.row)
這行代碼是移除一個Meal對象。這行代碼以後就是從table view中刪除對應的行
3.在MealTableViewController.swift中,找到tableView(_:canEditRowAtIndexPath:)方法,並取消註釋。它的模版實現以下
// Override to support conditional editing of the table view. override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { // Return false if you do not want the specified item to be editable. return true }
完整的tableView(_:commitEditingStyle:forRowAtIndexPath:)方法以下
// Override to support editing the table view. override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { // Delete the row from the data source meals.removeAtIndex(indexPath.row) tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) } else if editingStyle == .Insert { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view } }
檢查站:執行的APP。若是你點擊Edit按鈕,table view會進入編輯模式。你能經過左邊的指示器選擇一個cell來刪除,並確認你是否想要刪除它。或者在一個cell上滑動使刪除按鈕暴露。這個行爲默認內置在table view中。當你點擊Delete時,這個cell就會刪除。