重要:這是針對于正在開發(fā)中的API或技術(shù)的預(yù)備文檔(預(yù)發(fā)布版本)。蘋果提供這份文檔的目的是幫助你按照文中描述的方式對技術(shù)的選擇及界面的設(shè)計開發(fā)進行規(guī)劃。這些信息有可能發(fā)生變化,因此根據(jù)本文檔的軟件開發(fā)應(yīng)當基于最終版本的操作系統(tǒng)和文檔進行測試。該文檔的新版本或許會隨著API或相關(guān)技術(shù)未來的發(fā)展而進行更新。
翻譯自蘋果官網(wǎng):
本課中,集中精力添加功能讓用戶在 app 中能編輯和刪除美食。
學(xué)習(xí)目標
在本課的最后,你將能夠:
- 區(qū)別 push 和 modal 導(dǎo)航
- 基于視圖控制器的打開方式關(guān)閉它們
- 理解何時使用不同的向下轉(zhuǎn)換的方式
- 利用可選綁定來檢查復(fù)雜的條件
- 使用 segue 標識確定哪個 segue 正在發(fā)生。
允許編輯已存在的美食
現(xiàn)在,F(xiàn)oodTracker app 讓用戶能夠向食物列表添加一份新的食物。下一步,讓用戶能夠編輯已存在的食物。
用戶點擊單元格打開一個用食物信息填充的場景。用戶修改然后點擊 Save 按鈕,更新信息并覆寫食物列表中對應(yīng)對象。
配置 table view 單元格
-
如果輔助編輯器打開了,點擊 Standard 按鈕返回標準編輯器。
[圖片上傳失敗...(image-f118a-1608214610979)]
打開 Main.storyboard。
在畫板中,選擇 table view cell。
-
按住 Control 從 table view cell 拖動到美食場景。
[圖片上傳失敗...(image-c0d85f-1608214610979)]
在拖動結(jié)束的位置彈出標題為 Selection Segue 的快捷菜單。
[圖片上傳失敗...(image-a9a69f-1608214610979)]
選擇菜單中的 show。
-
在食物列表和食物場景間拖動,松開后會看到新的 segue。
[圖片上傳失敗...(image-e07768-1608214610979)]
使用 Command+減號(-)縮小畫板。
-
選擇畫板中新加的 segue。
[圖片上傳失敗...(image-184310-1608214610979)]
-
在屬性檢查器中的 Identifier 區(qū)域輸入 ShowDetail?;剀嚧_認。
[圖片上傳失敗...(image-2772be-1608214610979)]
當 segue 觸發(fā)了,push 食物視圖控制器到食物列表場景所在的導(dǎo)航棧中。
檢驗:運行 app。在食物列表場景,你應(yīng)該能夠點擊一個 table view cell 來導(dǎo)航到食物場景,但是場景的內(nèi)容是空的。當你點擊 table view 中已經(jīng)存在的單元格時候,你想要的是編輯它,而不是創(chuàng)建一個新的。
現(xiàn)在有兩個 segue 能跳到相同的場景了,所以你需要一種方法來區(qū)別添加一份新的食物或者編輯已存在的這兩種情況。
回憶之前任何 segue 執(zhí)行前都會被調(diào)用的 prepareForSegue(_:sender:) 方法。你可以使用這個方法來區(qū)別哪個 segue 正在執(zhí)行,然后在食物場景中顯示合適的信息。使用之前設(shè)給它們的 identifiers 來區(qū)分 segues。AddItem(model segue)和 ShowDeatil(show segue)。
區(qū)分哪個 segue 在執(zhí)行
打開 MealTableViewController.swift。
-
在 MealTableViewController.swift 中,找到并取消 prepareForSegue(_:sender:) 方法的注釋。
(為了取消方法的注釋,移除周圍的 /* 和 */ 字符。)
當你做完這些后,模板默認實現(xiàn)如下:
// 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. } -
刪除兩行的注釋,替換為 if 語句和 else 從句。
if segue.identifier == "ShowDetail" { } else if segue.identifier == "AddItem" { }代碼比較 segue identifier 和之前分配給它們的標識字符串。
-
在 if 語句(如果食物正在被編輯就執(zhí)行)中,添加如下代碼:
let mealDetailViewController = segue.destinationViewController as! MealViewController代碼嘗試使用強制類型轉(zhuǎn)換操作符(as!) 下轉(zhuǎn) segue 的目標視圖控制器到 MealViewController。注意這個操作符的最后是感嘆號(!)而不是問號(?)。這意味著執(zhí)行了強制類型轉(zhuǎn)換。如果轉(zhuǎn)換成功,將 segue.destinationViewController 轉(zhuǎn)換的 MealViewController 類型的值賦給局部變量 mealDetailViewController。如果轉(zhuǎn)換不成功,app 運行時應(yīng)該崩潰了。
只有確認轉(zhuǎn)換會成功才使用強制轉(zhuǎn)換 - 如果失敗,app 產(chǎn)生錯誤并崩潰,否則,請使用 as? 轉(zhuǎn)換。
-
在前一行的下面,添加另一個 if 語句(嵌套在第一個里面):
// Get the cell that generated this segue. if let selectedMealCell = sender as? MealTableViewCell { }代碼嘗試使用可選類型轉(zhuǎn)換符(as?)轉(zhuǎn)換 sender 給 MealCell。如果轉(zhuǎn)換成功,將 sender 轉(zhuǎn)換的 MealTableViewCell 類型的值賦給本地常量 selectedMealCell 然后 if 語句繼續(xù)執(zhí)行。如果轉(zhuǎn)換不成功,表達式值變?yōu)?nil 然后 if 語句不執(zhí)行。
-
在 if 語句里面,添加這些代碼:
let indexPath = tableView.indexPathForCell(selectedMealCell)! let selectedMeal = meals[indexPath.row] mealDetailViewController.meal = selectedMeal代碼為 table view 選中的單元格拿到對應(yīng)的 Meal 對象。然后賦給目標視圖控制器的 meal 屬性。
-
在之前添加的 else 從句里面,添加 print 語句:
print("Adding new meal.")盡管添加新的食物時候此方法不需要做什么事情,但是記錄將要發(fā)生很有用。
最后的 prepareForSegue(_:sender:) 方法應(yīng)該像這樣:
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.")
}
}
現(xiàn)在需要邏輯實現(xiàn),你需要在 MealViewController.swift 中做一些工作來確保界面正確的更新了。特別當 MealViewController 對象創(chuàng)建后,它的視圖應(yīng)該被它的 meal 屬性的數(shù)據(jù)填充,當這些數(shù)據(jù)存在的情況下?;貞浵伦詈线m做這類工作的地方就是在 viewDidLoad() 方法里面了。
更新 viewDidLoad 的實現(xiàn)
打開 MealViewController.swift。
-
在 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() } -
在 nameTextField.delegate 行的下面,添加這些代碼:
// 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 屬性非空設(shè)置 MealViewController 的每個視圖來顯示對應(yīng)的數(shù)據(jù),這只在編輯已存在的食物時候發(fā)生。
你的 viewDidLoad() 方法應(yīng)該像這樣:
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。你應(yīng)該能夠點擊一個單元格導(dǎo)航到食物場景中并看到填充了食物數(shù)據(jù)。但是如果點擊 Save,app 添加了新的食物而不是覆寫已經(jīng)存在的。下一步實現(xiàn)正確的行為。
[圖片上傳失敗...(image-c0cb01-1608214610979)]
為了覆寫食物列表中已經(jīng)存在的食物,你需要更新 unwindToMealList(_:) 方法來處理兩種不同的情況。一種情況,你需要添加一份新的食物,另一種情況,你需要替換已經(jīng)存在的那份?;貞浵逻@個方法只在用戶點擊保存按鈕的時候調(diào)用,所以你不需要在這個方法中處理 Cancel 按鈕。
更新 unwindToMealList(_:) 實現(xiàn)來添加或者替換食物
打開 MealTableViewController.swift。
-
在 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) } } -
在 if 語句的開頭,添加如下 if 語句:
if let selectedIndexPath = tableView.indexPathForSelectedRow { }代碼檢查 table view 中的行是否被選中。如果選中,意味著用戶點擊其中的一個單元格來編輯食物。
-
在 if 語句中添加如下代碼:
// Update an existing meal. meals[selectedIndexPath.row] = meal tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)第一行更新 meals 中對應(yīng)的食物信息。第二行刷新 table view 中合適的行來顯示修改后的數(shù)據(jù)。
-
在 if 語句后面,添加如下的 else 從句:
else { // Add a new meal. let newIndexPath = NSIndexPath(forRow: meals.count, inSection: 0) meals.append(meal) tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Bottom) }選擇 else 從句中這些行按 Control-I 確保它們正確縮進。
當在 table view 中沒有選中行時執(zhí)行 else 從句,這意味著用戶點擊增加按鈕來打開食物場景。換言之,就是如果正在添加一份新的食物 else 從句就會執(zhí)行。
最后的 unwindToMealList(_:) 方法應(yīng)該像這樣:
@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。你應(yīng)該能夠點擊一個 table view cell 來導(dǎo)航到食物場景并看到它已經(jīng)被食物數(shù)據(jù)填充了。如果你點擊保存,你做的修改會覆寫列表中現(xiàn)有的食物。
[圖片上傳失敗...(image-906f1a-1608214610979)]
取消編輯現(xiàn)有的食物
用戶或許決定不再編輯食物了,想在沒有保存任何修改的情況下返回食物列表。為了做到這點,更新 Cancel 按鈕的行為來適當?shù)年P(guān)閉場景。
關(guān)閉的類型取決于打開時的類型。當用戶點擊 Cancel 按鈕時做一個檢查來決定當前場景如何打開的。如果是模態(tài)打開的(使用 Add 按鈕),使用 dismissViewControllerAnimated(_:completion:) 關(guān)閉。如果使用 push 導(dǎo)航打開的(點擊單元格),會被那個打開它的導(dǎo)航控制器關(guān)閉的。
修改取消方法
打開 MealViewController.swift。
-
在 MealViewController.swift 中,找到 cancel(_:) 方法。
@IBAction func cancel(sender: UIBarButtonItem) { dismissViewControllerAnimated(true, completion: nil) }當前只使用了
dismissViewControllerAnimated來關(guān)閉食物場景因為你目前僅僅為 Add 按鈕做處理了。 -
在 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代碼創(chuàng)建一個布爾值表明打開這個場景視圖控制器是否為 UINavigationController 類型。正如常量名字 isPresentingInAddMealMode 說明,這意味著食物場景使用 Add 按鈕打開的。因為當食物場景以這種方式打開時候它被嵌入到它自己的導(dǎo)航控制器中,這意味著是導(dǎo)航控制器打開了它。
-
在你剛添加的行下面,添加如下 if 語句,并且移動 dismissViewControllerAnimated 那行到它里面:
if isPresentingInAddMealMode { dismissViewControllerAnimated(true, completion: nil) }之前,dismissViewControllerAnimated 每次都會在 cancel(_:) 方法觸發(fā)時調(diào)用,現(xiàn)在只在 isPresentingInAddMealMode 是 true 的情況下執(zhí)行。
-
在 if 語句的右邊,添加如下 else 從句:
else { navigationController!.popViewControllerAnimated(true) }現(xiàn)在有 if 語句同時有個 else 從句,if 語句中的代碼只會在 isPresentingInAddMealMode 是 true 的情況下執(zhí)行,否則執(zhí)行 else 從句的代碼。當食物場景被 push 到導(dǎo)航棧時 else 從句執(zhí)行。else 從句中代碼調(diào)用
popViewControllerAnimated從導(dǎo)航棧中彈出當前視圖控制器(食物場景)并同時執(zhí)行一個過渡動畫。
最后的 cancel(_:) 方法應(yīng)該像這樣:
@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。現(xiàn)在當你點擊 Add 按鈕(+)然后點擊 Cancel 而不是 Save,你應(yīng)該導(dǎo)航返回到食物列表了。
支持刪除食物
下一步,讓用戶能夠從食物列表中刪除一份食物。需要用戶能夠?qū)?table view 變成編輯模式來刪除單元格。通過在 table view 導(dǎo)航欄添加一個編輯按鈕來完成這個功能。
為 table view 添加編輯按鈕
打開 MealTableViewController.swift。
-
在 MealTableViewController.swift 中,找到 viewDidLoad() 方法。
override func viewDidLoad() { super.viewDidLoad() // Load the sample data. loadSampleMeals() } -
在 super.viewDidLoad() 行的下面,添加下面這行代碼:
// Use the edit button item provided by the table view controller. navigationItem.leftBarButtonItem = editButtonItem()創(chuàng)建了一個特別的 bar button item 內(nèi)置具有編輯功能。之后添加這個按鈕到食物場景導(dǎo)航欄的左邊。
viewDidLoad() 方法應(yīng)該像這樣:
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。注意在 table view 導(dǎo)航欄的左邊有個編輯按鈕。如果點擊編輯按鈕,table view 變成編輯模式-但是你還是沒法刪除單元格,因為你沒有實現(xiàn)編輯操作。
為了執(zhí)行在 table view 的編輯操作,你需要實現(xiàn)其中一個代理方法,tableView(_:commitEditingStyle:forRowAtIndexPath:)。這個代理方法負責(zé)在編輯模式時管理表格行。
你也需要取消 tableView(_:canEditRowAtIndexPath:) 的注釋來支持編輯。
刪除一份食物
-
在 MealTableViewController.swift 中,找到并取消
tableView(_:commitEditingStyle:forRowAtIndexPath:)的注釋。(為了取消這個方法的注釋,移除它周圍的 /* 和 */ 字符。)當你做了這些,模板默認實現(xiàn)如下:
// 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 } } -
在
// Delete the row from the data source這行注釋的下面,添加下面這行代碼:meals.removeAtIndex(indexPath.row) -
在
MealTableViewController.swift中,找到并取消tableView(_:canEditRowAtIndexPath:)方法的注釋。做完后,模板默認實現(xiàn)如下:
// 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:) 方法應(yīng)該像這樣:
// 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。如果點擊編輯按鈕,table view 變成編輯模式??梢赃x擇一個單元格通過點擊左邊的指示器來刪除,然后通過按單元格的 Delete 按鈕確認刪除它?;蛘撸蜃笄鍜咭粋€單元格來快速打開 Delete 按鈕;這是 table view 內(nèi)置的功能。當你點擊單元格刪除按鈕,它就從列表中刪除了。
[圖片上傳失敗...(image-670155-1608214610979)]
注意
為了看到本課的完整的示例項目,下載文件并在 Xcode 中查看它。