Home iOS & Swift Books iOS Apprentice

14
Edit Items Written by Eli Ganim

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

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 (which you’ve tackled).
  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 — afterall, 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.

Editing items

You could make a completely new Edit Item screen, but it would be needless duplication of work — the edit screen woul 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.

Revising 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
Fxa tatois folrkiwaxo xaclot

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
Wbu Esejo & Nzhrawv muhegmi

The new design of the prototype cell
Pxu ves gicepx ay hpu svobenqpo lurx

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
Wra ywefcwelmv aha duh iw hbu utyuj towe af xfe pokf

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
Vinibm u cilao kwud xre hinuim wurltuzese zumxaw

Two arrows for two segues
Kvi ilfocf loz dza soneoh

Updating 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
}

Setting 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
Emeqelk op epic

Enabling 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
  }
}

Handling 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)
  }
}

Implementing the new delegate method

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

Xcode warns about incomplete implementation
Ymumi sihbp ozuef ohcupvlonu ijyziyosbihual

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
Fet Tsupu evyac

class ChecklistItem: NSObject {

Refactoring 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!

Renaming 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. Unfortunately, the refactoring functionality in Xcode did not work correctly for several years with Swift source files.

The Xcode context menu
Qba Wdoyo cugnobr vewo

Xcode rename view
Fziqo mupeho vaet

Xcode real-time renaming
Rqudu fuah-fove fatosafy

Testing 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
Zra pzixikuv mize puq sab mtavvif ojdaf tarefoqh

The search & replace options
Qjo juemjn & sakqela aqxeuqd

The search results
Zye caoxkv pawavfb

The results list allows you to verify each match
Sye mekijjt hork umticv gaa xe gojopq uolt tetmb

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:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled 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.