重要:這是針對(duì)于正在開發(fā)中的API或技術(shù)的預(yù)備文檔(預(yù)發(fā)布版本)囚霸。蘋果提供這份文檔的目的是幫助你按照文中描述的方式對(duì)技術(shù)的選擇及界面的設(shè)計(jì)開發(fā)進(jìn)行規(guī)劃拓型。這些信息有可能發(fā)生變化,因此根據(jù)本文檔的軟件開發(fā)應(yīng)當(dāng)基于最終版本的操作系統(tǒng)和文檔進(jìn)行測(cè)試册养。該文檔的新版本或許會(huì)隨著API或相關(guān)技術(shù)未來的發(fā)展而進(jìn)行更新揣云。
翻譯自蘋果官網(wǎng):
本課中,集中精力添加功能讓用戶在 app 中能編輯和刪除美食刘莹。
學(xué)習(xí)目標(biāo)
在本課的最后焚刚,你將能夠:
- 區(qū)別 push 和 modal 導(dǎo)航
- 基于視圖控制器的打開方式關(guān)閉它們
- 理解何時(shí)使用不同的向下轉(zhuǎn)換的方式
- 利用可選綁定來檢查復(fù)雜的條件
- 使用 segue 標(biāo)識(shí)確定哪個(gè) segue 正在發(fā)生矿咕。
允許編輯已存在的美食
現(xiàn)在,F(xiàn)oodTracker app 讓用戶能夠向食物列表添加一份新的食物捡絮。下一步莲镣,讓用戶能夠編輯已存在的食物。
用戶點(diǎn)擊單元格打開一個(gè)用食物信息填充的場(chǎng)景的圆。用戶修改然后點(diǎn)擊 Save 按鈕,更新信息并覆寫食物列表中對(duì)應(yīng)對(duì)象季俩。
配置 table view 單元格
-
如果輔助編輯器打開了梅掠,點(diǎn)擊 Standard 按鈕返回標(biāo)準(zhǔn)編輯器瓤檐。
[圖片上傳失敗...(image-f118a-1608214610979)]
打開 Main.storyboard娱节。
在畫板中,選擇 table view cell谴古。
-
按住 Control 從 table view cell 拖動(dòng)到美食場(chǎng)景稠歉。
[圖片上傳失敗...(image-c0d85f-1608214610979)]
在拖動(dòng)結(jié)束的位置彈出標(biāo)題為 Selection Segue 的快捷菜單怒炸。
[圖片上傳失敗...(image-a9a69f-1608214610979)]
選擇菜單中的 show。
-
在食物列表和食物場(chǎng)景間拖動(dòng)勺疼,松開后會(huì)看到新的 segue捏鱼。
[圖片上傳失敗...(image-e07768-1608214610979)]
使用 Command+減號(hào)(-)縮小畫板导梆。
-
選擇畫板中新加的 segue。
[圖片上傳失敗...(image-184310-1608214610979)]
-
在屬性檢查器中的 Identifier 區(qū)域輸入 ShowDetail递鹉〔卣叮回車確認(rèn)灾茁。
[圖片上傳失敗...(image-2772be-1608214610979)]
當(dāng) segue 觸發(fā)了谷炸,push 食物視圖控制器到食物列表場(chǎng)景所在的導(dǎo)航棧中旬陡。
檢驗(yàn):運(yùn)行 app语婴。在食物列表場(chǎng)景,你應(yīng)該能夠點(diǎn)擊一個(gè) table view cell 來導(dǎo)航到食物場(chǎng)景匿醒,但是場(chǎng)景的內(nèi)容是空的廉羔。當(dāng)你點(diǎn)擊 table view 中已經(jīng)存在的單元格時(shí)候僻造,你想要的是編輯它,而不是創(chuàng)建一個(gè)新的竹挡。
現(xiàn)在有兩個(gè) segue 能跳到相同的場(chǎng)景了立膛,所以你需要一種方法來區(qū)別添加一份新的食物或者編輯已存在的這兩種情況。
回憶之前任何 segue 執(zhí)行前都會(huì)被調(diào)用的 prepareForSegue(_:sender:) 方法好啰。你可以使用這個(gè)方法來區(qū)別哪個(gè) segue 正在執(zhí)行鲁猩,然后在食物場(chǎng)景中顯示合適的信息廓握。使用之前設(shè)給它們的 identifiers 來區(qū)分 segues。AddItem(model segue)和 ShowDeatil(show segue)男应。
區(qū)分哪個(gè) segue 在執(zhí)行
打開 MealTableViewController.swift娱仔。
-
在 MealTableViewController.swift 中,找到并取消 prepareForSegue(_:sender:) 方法的注釋耐朴。
(為了取消方法的注釋筛峭,移除周圍的 /* 和 */ 字符。)
當(dāng)你做完這些后镰吵,模板默認(rèn)實(shí)現(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 語(yǔ)句和 else 從句饵婆。
if segue.identifier == "ShowDetail" { } else if segue.identifier == "AddItem" { }
代碼比較 segue identifier 和之前分配給它們的標(biāo)識(shí)字符串啦辐。
-
在 if 語(yǔ)句(如果食物正在被編輯就執(zhí)行)中蜈项,添加如下代碼:
let mealDetailViewController = segue.destinationViewController as! MealViewController
代碼嘗試使用強(qiáng)制類型轉(zhuǎn)換操作符(as!) 下轉(zhuǎn) segue 的目標(biāo)視圖控制器到 MealViewController紧卒。注意這個(gè)操作符的最后是感嘆號(hào)(!)而不是問號(hào)(?)恨锚。這意味著執(zhí)行了強(qiáng)制類型轉(zhuǎn)換白粉。如果轉(zhuǎn)換成功空猜,將 segue.destinationViewController 轉(zhuǎn)換的 MealViewController 類型的值賦給局部變量 mealDetailViewController。如果轉(zhuǎn)換不成功往堡,app 運(yùn)行時(shí)應(yīng)該崩潰了虑灰。
只有確認(rèn)轉(zhuǎn)換會(huì)成功才使用強(qiáng)制轉(zhuǎn)換 - 如果失敗痹兜,app 產(chǎn)生錯(cuò)誤并崩潰,否則对湃,請(qǐng)使用 as拍柒? 轉(zhuǎn)換。
-
在前一行的下面剧包,添加另一個(gè) if 語(yǔ)句(嵌套在第一個(gè)里面):
// 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 語(yǔ)句繼續(xù)執(zhí)行。如果轉(zhuǎn)換不成功掉缺,表達(dá)式值變?yōu)?nil 然后 if 語(yǔ)句不執(zhí)行眶明。
-
在 if 語(yǔ)句里面筐高,添加這些代碼:
let indexPath = tableView.indexPathForCell(selectedMealCell)! let selectedMeal = meals[indexPath.row] mealDetailViewController.meal = selectedMeal
代碼為 table view 選中的單元格拿到對(duì)應(yīng)的 Meal 對(duì)象柑土。然后賦給目標(biāo)視圖控制器的 meal 屬性。
-
在之前添加的 else 從句里面扮宠,添加 print 語(yǔ)句:
print("Adding new meal.")
盡管添加新的食物時(shí)候此方法不需要做什么事情坛增,但是記錄將要發(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)在需要邏輯實(shí)現(xiàn)被廓,你需要在 MealViewController.swift 中做一些工作來確保界面正確的更新了。特別當(dāng) MealViewController 對(duì)象創(chuàng)建后昆婿,它的視圖應(yīng)該被它的 meal 屬性的數(shù)據(jù)填充仓蛆,當(dāng)這些數(shù)據(jù)存在的情況下《共Γ回憶下最合適做這類工作的地方就是在 viewDidLoad() 方法里面了能庆。
更新 viewDidLoad 的實(shí)現(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 的每個(gè)視圖來顯示對(duì)應(yīng)的數(shù)據(jù)承耿,這只在編輯已存在的食物時(shí)候發(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()
}
檢驗(yàn):運(yùn)行 app抱既。你應(yīng)該能夠點(diǎn)擊一個(gè)單元格導(dǎo)航到食物場(chǎng)景中并看到填充了食物數(shù)據(jù)扁誓。但是如果點(diǎn)擊 Save蝗敢,app 添加了新的食物而不是覆寫已經(jīng)存在的。下一步實(shí)現(xiàn)正確的行為锁右。
[圖片上傳失敗...(image-c0cb01-1608214610979)]
為了覆寫食物列表中已經(jīng)存在的食物咏瑟,你需要更新 unwindToMealList(_:) 方法來處理兩種不同的情況。一種情況兄旬,你需要添加一份新的食物余寥,另一種情況宋舷,你需要替換已經(jīng)存在的那份×ぃ回憶下這個(gè)方法只在用戶點(diǎn)擊保存按鈕的時(shí)候調(diào)用改艇,所以你不需要在這個(gè)方法中處理 Cancel 按鈕坟岔。
更新 unwindToMealList(_:) 實(shí)現(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 語(yǔ)句的開頭啊研,添加如下 if 語(yǔ)句:
if let selectedIndexPath = tableView.indexPathForSelectedRow { }
代碼檢查 table view 中的行是否被選中党远。如果選中富弦,意味著用戶點(diǎn)擊其中的一個(gè)單元格來編輯食物。
-
在 if 語(yǔ)句中添加如下代碼:
// Update an existing meal. meals[selectedIndexPath.row] = meal tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
第一行更新 meals 中對(duì)應(yīng)的食物信息济似。第二行刷新 table view 中合適的行來顯示修改后的數(shù)據(jù)碱屁。
-
在 if 語(yǔ)句后面娩脾,添加如下的 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 確保它們正確縮進(jìn)。
當(dāng)在 table view 中沒有選中行時(shí)執(zhí)行 else 從句俩功,這意味著用戶點(diǎn)擊增加按鈕來打開食物場(chǎng)景诡蜓。換言之胰挑,就是如果正在添加一份新的食物 else 從句就會(huì)執(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)
}
}
}
檢驗(yàn):運(yùn)行 app贡这。你應(yīng)該能夠點(diǎn)擊一個(gè) table view cell 來導(dǎo)航到食物場(chǎng)景并看到它已經(jīng)被食物數(shù)據(jù)填充了。如果你點(diǎn)擊保存丽惭,你做的修改會(huì)覆寫列表中現(xiàn)有的食物责掏。
[圖片上傳失敗...(image-906f1a-1608214610979)]
取消編輯現(xiàn)有的食物
用戶或許決定不再編輯食物了辐马,想在沒有保存任何修改的情況下返回食物列表喜爷。為了做到這點(diǎn)萄唇,更新 Cancel 按鈕的行為來適當(dāng)?shù)年P(guān)閉場(chǎng)景另萤。
關(guān)閉的類型取決于打開時(shí)的類型诅挑。當(dāng)用戶點(diǎn)擊 Cancel 按鈕時(shí)做一個(gè)檢查來決定當(dāng)前場(chǎng)景如何打開的拔妥。如果是模態(tài)打開的(使用 Add 按鈕)达箍,使用 dismissViewControllerAnimated(_:completion:) 關(guān)閉缎玫。如果使用 push 導(dǎo)航打開的(點(diǎn)擊單元格)赃磨,會(huì)被那個(gè)打開它的導(dǎo)航控制器關(guān)閉的。
修改取消方法
打開 MealViewController.swift溪王。
-
在 MealViewController.swift 中莹菱,找到 cancel(_:) 方法雷客。
@IBAction func cancel(sender: UIBarButtonItem) { dismissViewControllerAnimated(true, completion: nil) }
當(dāng)前只使用了
dismissViewControllerAnimated
來關(guān)閉食物場(chǎng)景因?yàn)槟隳壳皟H僅為 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)建一個(gè)布爾值表明打開這個(gè)場(chǎng)景視圖控制器是否為 UINavigationController 類型娜汁。正如常量名字 isPresentingInAddMealMode 說明掐禁,這意味著食物場(chǎng)景使用 Add 按鈕打開的傅事。因?yàn)楫?dāng)食物場(chǎng)景以這種方式打開時(shí)候它被嵌入到它自己的導(dǎo)航控制器中峡扩,這意味著是導(dǎo)航控制器打開了它教届。
-
在你剛添加的行下面驾霜,添加如下 if 語(yǔ)句粪糙,并且移動(dòng) dismissViewControllerAnimated 那行到它里面:
if isPresentingInAddMealMode { dismissViewControllerAnimated(true, completion: nil) }
之前蓉冈,dismissViewControllerAnimated 每次都會(huì)在 cancel(_:) 方法觸發(fā)時(shí)調(diào)用倦卖,現(xiàn)在只在 isPresentingInAddMealMode 是 true 的情況下執(zhí)行怕膛。
-
在 if 語(yǔ)句的右邊,添加如下 else 從句:
else { navigationController!.popViewControllerAnimated(true) }
現(xiàn)在有 if 語(yǔ)句同時(shí)有個(gè) else 從句掸茅,if 語(yǔ)句中的代碼只會(huì)在 isPresentingInAddMealMode 是 true 的情況下執(zhí)行昧狮,否則執(zhí)行 else 從句的代碼逗鸣。當(dāng)食物場(chǎng)景被 push 到導(dǎo)航棧時(shí) else 從句執(zhí)行绰精。else 從句中代碼調(diào)用
popViewControllerAnimated
從導(dǎo)航棧中彈出當(dāng)前視圖控制器(食物場(chǎng)景)并同時(shí)執(zhí)行一個(gè)過渡動(dòng)畫笨使。
最后的 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)
}
}
檢驗(yàn):運(yùn)行 app×蛞現(xiàn)在當(dāng)你點(diǎn)擊 Add 按鈕(+)然后點(diǎn)擊 Cancel 而不是 Save靶草,你應(yīng)該導(dǎo)航返回到食物列表了。
支持刪除食物
下一步烤送,讓用戶能夠從食物列表中刪除一份食物帮坚。需要用戶能夠?qū)?table view 變成編輯模式來刪除單元格互艾。通過在 table view 導(dǎo)航欄添加一個(gè)編輯按鈕來完成這個(gè)功能纫普。
為 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)建了一個(gè)特別的 bar button item 內(nèi)置具有編輯功能简卧。之后添加這個(gè)按鈕到食物場(chǎng)景導(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()
}
檢驗(yàn):運(yùn)行 app构罗。注意在 table view 導(dǎo)航欄的左邊有個(gè)編輯按鈕绰播。如果點(diǎn)擊編輯按鈕,table view 變成編輯模式-但是你還是沒法刪除單元格链蕊,因?yàn)槟銢]有實(shí)現(xiàn)編輯操作滔韵。
為了執(zhí)行在 table view 的編輯操作陪蜻,你需要實(shí)現(xiàn)其中一個(gè)代理方法贱鼻,tableView(_:commitEditingStyle:forRowAtIndexPath:)。這個(gè)代理方法負(fù)責(zé)在編輯模式時(shí)管理表格行随闽。
你也需要取消 tableView(_:canEditRowAtIndexPath:) 的注釋來支持編輯。
刪除一份食物
-
在 MealTableViewController.swift 中肝谭,找到并取消
tableView(_:commitEditingStyle:forRowAtIndexPath:)
的注釋掘宪。(為了取消這個(gè)方法的注釋,移除它周圍的 /* 和 */ 字符攘烛。)當(dāng)你做了這些魏滚,模板默認(rèn)實(shí)現(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:)
方法的注釋鼠次。做完后,模板默認(rèn)實(shí)現(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
}
}
檢驗(yàn):運(yùn)行 app须眷。如果點(diǎn)擊編輯按鈕,table view 變成編輯模式沟突』牛可以選擇一個(gè)單元格通過點(diǎn)擊左邊的指示器來刪除,然后通過按單元格的 Delete 按鈕確認(rèn)刪除它惠拭±┤埃或者,向左清掃一個(gè)單元格來快速打開 Delete 按鈕职辅;這是 table view 內(nèi)置的功能棒呛。當(dāng)你點(diǎn)擊單元格刪除按鈕,它就從列表中刪除了域携。
[圖片上傳失敗...(image-670155-1608214610979)]
注意
為了看到本課的完整的示例項(xiàng)目簇秒,下載文件并在 Xcode 中查看它。