Home iOS & Swift Books UIKit Apprentice

14
Edit Items Written by Matthijs Hollemans & Fahim Farook

Adding new items to the list is a great step forward for the app, but there are usually three things an app needs to do with data:

  1. Add new items — you’ve tackled this already.
  2. Deleting items — you allow that with swipe-to-delete.
  3. Editing existing items — uhh…

The last is useful when you want to rename an item from your list — after all, we all make typos.

This chapter covers the following:

  • Edit items: Edit existing to-do items via the app interface.
  • Refactor the code: Using Xcode’s built-in refactoring capability to rename code to be easily identifiable.
  • One more thing: Fix missed code changes after the code refactoring using the Find navigator.

Edit items

You could make a completely new Edit Item screen, but it would be needless duplication of work — the edit screen would work mostly the same as the Add Item screen. The only difference is that it doesn’t start out empty — instead, it works with an existing to-do item.

So, let’s re-use the Add Item screen and make it capable of editing an existing ChecklistItem object.

Editing a to-do item
Editing a to-do item

For the edit option, when the user presses Done, you won’t have to make a new ChecklistItem object, instead, you will simply update the text in the existing ChecklistItem.

You’ll also tell the delegate about these changes so that it can update the text label of the corresponding table view cell.

Exercise: What changes would you need to make to the Add Item screen to enable it to edit existing items?

Answer:

  1. The screen title must be changed to Edit Item.
  2. You must be able to pass it an existing ChecklistItem object.
  3. You have to place the ChecklistItem’s text into the text field.
  4. When the user presses Done, you should not add a new ChecklistItem object, but instead, update the existing one.

There is a bit of a user interface problem, though… How will the user actually open the Edit Item screen? In many apps that is done by tapping on the item’s row, but in Checklists that already toggles the checkmark on or off.

To solve this problem, you’ll have to revise the UI a little first.

Revise the UI to allow editing

When a row is given two functions, the standard approach is to use a detail disclosure button for the secondary task:

The detail disclosure button
Bri qohouv hajfzoqixo luqqim

The new checkmark

➤ Drag a new Label on to the cell and place it to the left of the text label. Give it the following attributes:

The Emoji & Symbols palette
Gza Uvuga & Dnsbizs huhapke

The new design of the prototype cell
Wti gaq kuhiyq ix xve xhevoqdfa yihh

func configureCheckmark(
  for cell: UITableViewCell, 
  with item: ChecklistItem
) {
  let label = cell.viewWithTag(1001) as! UILabel

  if item.checked {
    label.text = "√"
  } else {
    label.text = ""
  }
}    
The checkmarks are now on the other side of the cell
Fxi wrodqfatct ura vuq ep rze irnex leki es vmi dicf

The edit screen segue

Next, you’re going to make the detail disclosure button open the Add/Edit Item screen. This is pretty simple because Interface Builder also allows you to make a segue for a disclosure button.

Making a segue from the detail disclosure button
Kohinv o jozao xviz vce moqook weynmotihu mejliz

Two arrows for two segues
Dfu itmufp xum dca xeguob

Update the Add Item screen to handle editing

➤ Add a new property for a ChecklistItem object below the other instance variables in AddItemViewController.swift:

var itemToEdit: ChecklistItem?
override func viewDidLoad() {
  . . .
  if let item = itemToEdit {
    title = "Edit Item"
    textField.text = item.text
  }
}

if let

You cannot use optionals like you would regular variables. For example, if viewDidLoad() had the following code:

    textField.text = itemToEdit.text
if let temporaryConstant = optionalVariable {
  // temporaryConstant now contains the unwrapped value of the 
  // optional variable. temporayConstant is only available from
  // within this if block
}
if let itemToEdit = itemToEdit {
  title = "Edit Item"
  textField.text = itemToEdit.text
}

Set the item to be edited

➤ Change prepare(for:sender:) in ChecklistViewController.swift to the following:

override func prepare(
  for segue: UIStoryboardSegue, 
  sender: Any?
) {
  if segue.identifier == "AddItem" {
    . . .

  } else if segue.identifier == "EditItem" {
    let controller = segue.destination as! AddItemViewController
    controller.delegate = self

    if let indexPath = tableView.indexPath(
      for: sender as! UITableViewCell) {
      controller.itemToEdit = items[indexPath.row]
    }
  }
}
if let indexPath = tableView.indexPath(for: sender as! UITableViewCell){
  controller.itemToEdit = items[indexPath.row]
}

Sending data between view controllers

We’ve talked about screen B (the Add/Edit Item screen) passing data back to screen A (the Checklists screen) via delegates. But here, you’re passing a piece of data the other way around – from screen A to screen B – namely, the ChecklistItem to edit.

Editing an item
Iyagudh ad odug

Enable the Done button for edits

One small problem: the Done button in the navigation bar is initially disabled. This is because you originally set it to be disabled in the storyboard.

override func viewDidLoad() {
  super.viewDidLoad()

  if let item = itemToEdit {
    title = "Edit Item"
    textField.text = item.text
    doneBarButton.isEnabled = true    // add this line
  }
}

Handle edits in the delegate protocol

➤ Add the following line to the protocol section in AddItemViewController.swift:

func addItemViewController(
  _ controller: AddItemViewController, 
  didFinishEditing item: ChecklistItem
)
protocol AddItemViewControllerDelegate: class {
  func addItemViewControllerDidCancel(
    _ controller: AddItemViewController)
  func addItemViewController(
    _ controller: AddItemViewController,
    didFinishAdding item: ChecklistItem
  )
  func addItemViewController(
    _ controller: AddItemViewController,
    didFinishEditing item: ChecklistItem
  )
}
@IBAction func done() {
  if let item = itemToEdit {
    item.text = textField.text!
    delegate?.addItemViewController(
      self, 
      didFinishEditing: item)
  } else {
    let item = ChecklistItem()
    item.text = textField.text!
    delegate?.addItemViewController(self, didFinishAdding: item)
  }
}

Implement the new delegate method

➤ Try to build the app. It won’t work.

Xcode warns about incomplete implementation
Kqufi kamhv ekeic ofnarpqesi ircqicopmopeow

func addItemViewController(
  _ controller: AddItemViewController, 
  didFinishEditing item: ChecklistItem
) {
  if let index = items.firstIndex(of: item) {
    let indexPath = IndexPath(row: index, section: 0)
    if let cell = tableView.cellForRow(at: indexPath) {
      configureText(for: cell, with: item)
    }
  }
  navigationController?.popViewController(animated: true)
}
if let index = items.firstIndex(of: item) {
New Xcode error
Meg Xkazu exfuh

class ChecklistItem: NSObject {

Refactor the code

At this point, you have an app that can add new items and edit existing items using the combined Add/Edit Item screen. Pretty sweet!

Rename the view controller

Most IDEs (or Integrated Development Environments) such as Xcode have a feature named refactoring, which allows you to change the name of a class, method, or variable throughout the entire project, safely.

The Xcode context menu
Xvi Mdavi xuljutl muce

Xcode rename view
Nmaze qageli yaow

Test the code after a refactor

Let’s see if everything works correctly now.

One more thing

The rename process appears to have gone through flawlessly, your app works fine when you test it, and there are no crashes. So, everything should be fine and you can move on to the next feature in the app, right?

The protocol name has not changed after renaming
Myu lwudezil bize qex ruw bgusvel agxag gihakurc

The search & replace options
Lnu xeikrz & qowdiqa ulduamh

The search results
Stu viezss nanellz

The results list allows you to verify each match
Kja muhubyf gurw etrimb faa si fuburd uotw recry

Iterative development

If you think this approach to development we’ve taken so far is a little messy, then you’re absolutely right. You started out with one design, but as you continued development you found out that things didn’t work out so well in practice, and that you had to refactor your approach a few times to find a way that works. This is actually how software development goes in practice.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.