馬上著手開發(fā) iOS 應(yīng)用程序 (十) - 實現(xiàn)編輯和刪除功能

重要:這是針對于正在開發(fā)中的API或技術(shù)的預(yù)備文檔(預(yù)發(fā)布版本)。蘋果提供這份文檔的目的是幫助你按照文中描述的方式對技術(shù)的選擇及界面的設(shè)計開發(fā)進行規(guī)劃。這些信息有可能發(fā)生變化,因此根據(jù)本文檔的軟件開發(fā)應(yīng)當基于最終版本的操作系統(tǒng)和文檔進行測試。該文檔的新版本或許會隨著API或相關(guān)技術(shù)未來的發(fā)展而進行更新。

翻譯自蘋果官網(wǎng):

https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson9.html#//apple_ref/doc/uid/TP40015214-CH9-SW1

本課中,集中精力添加功能讓用戶在 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 單元格
  1. 如果輔助編輯器打開了,點擊 Standard 按鈕返回標準編輯器。

    [圖片上傳失敗...(image-f118a-1608214610979)]

  2. 打開 Main.storyboard。

  3. 在畫板中,選擇 table view cell。

  4. 按住 Control 從 table view cell 拖動到美食場景。

    [圖片上傳失敗...(image-c0d85f-1608214610979)]

    在拖動結(jié)束的位置彈出標題為 Selection Segue 的快捷菜單。

    [圖片上傳失敗...(image-a9a69f-1608214610979)]

  5. 選擇菜單中的 show。

  6. 在食物列表和食物場景間拖動,松開后會看到新的 segue。

    [圖片上傳失敗...(image-e07768-1608214610979)]

    使用 Command+減號(-)縮小畫板。

  7. 選擇畫板中新加的 segue。

    [圖片上傳失敗...(image-184310-1608214610979)]

  8. 在屬性檢查器中的 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í)行
  1. 打開 MealTableViewController.swift。

  2. 在 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.
     }
    
  3. 刪除兩行的注釋,替換為 if 語句和 else 從句。

     if segue.identifier == "ShowDetail" {
     }
     else if segue.identifier == "AddItem" {
     }
    

    代碼比較 segue identifier 和之前分配給它們的標識字符串。

  4. 在 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)換。

  5. 在前一行的下面,添加另一個 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í)行。

  6. 在 if 語句里面,添加這些代碼:

     let indexPath = tableView.indexPathForCell(selectedMealCell)!
     let selectedMeal = meals[indexPath.row]
     mealDetailViewController.meal = selectedMeal
    

    代碼為 table view 選中的單元格拿到對應(yīng)的 Meal 對象。然后賦給目標視圖控制器的 meal 屬性。

  7. 在之前添加的 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)
  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 行的下面,添加這些代碼:

     // 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)來添加或者替換食物
  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 語句:

     if let selectedIndexPath = tableView.indexPathForSelectedRow {
     }
    

    代碼檢查 table view 中的行是否被選中。如果選中,意味著用戶點擊其中的一個單元格來編輯食物。

  4. 在 if 語句中添加如下代碼:

     // Update an existing meal.
     meals[selectedIndexPath.row] = meal
     tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
    

    第一行更新 meals 中對應(yīng)的食物信息。第二行刷新 table view 中合適的行來顯示修改后的數(shù)據(jù)。

  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 從句中這些行按 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)閉的。

修改取消方法
  1. 打開 MealViewController.swift。

  2. 在 MealViewController.swift 中,找到 cancel(_:) 方法。

     @IBAction func cancel(sender: UIBarButtonItem) {
         dismissViewControllerAnimated(true, completion: nil)
     }
    

    當前只使用了 dismissViewControllerAnimated 來關(guān)閉食物場景因為你目前僅僅為 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
    

    代碼創(chuàng)建一個布爾值表明打開這個場景視圖控制器是否為 UINavigationController 類型。正如常量名字 isPresentingInAddMealMode 說明,這意味著食物場景使用 Add 按鈕打開的。因為當食物場景以這種方式打開時候它被嵌入到它自己的導(dǎo)航控制器中,這意味著是導(dǎo)航控制器打開了它。

  4. 在你剛添加的行下面,添加如下 if 語句,并且移動 dismissViewControllerAnimated 那行到它里面:

     if isPresentingInAddMealMode {
         dismissViewControllerAnimated(true, completion: nil)
     }
    

    之前,dismissViewControllerAnimated 每次都會在 cancel(_:) 方法觸發(fā)時調(diào)用,現(xiàn)在只在 isPresentingInAddMealMode 是 true 的情況下執(zhí)行。

  5. 在 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 添加編輯按鈕
  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()
    

    創(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:) 的注釋來支持編輯。

刪除一份食物
  1. 在 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
         }
     }
    
  2. // Delete the row from the data source 這行注釋的下面,添加下面這行代碼:

     meals.removeAtIndex(indexPath.row)
    
  3. 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 中查看它。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容