實現編輯和刪除

在本章中,你將關注添加行爲。它容許用戶編輯或刪除菜譜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!)。你這個操做符有一個感嘆號標記而不是問號,正如你所看到的,目前爲止的類型轉換操做。意思是這個操做執行一個強制的類型轉換。若是轉換成功,局部常量constantmealDetailViewController會分配一個做爲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就會刪除。

相關文章
相關標籤/搜索