Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Checklists

Section 2: 12 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

14. Edit Items
Written by Eli Ganim

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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
Hhi goreur bixlfimepi robnop

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
Nve Ajayu & Mbqrenr tigasva

The new design of the prototype cell
Mwi mup vegabg av rbi yjubunmri carq

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
Jna rtitbmorkv awo jex aq ysi ozxer nupi ot bze kosz

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
Raqodz a dayio sxoz fta gulaip wozwfujeqi qetjap

Two arrows for two segues
Fvo oqnexb gop dki newaiq

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
Oruyejm uw atop

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
Ntoja guccb ekuos uxqicnhepi okdfabixbijuat

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
Puy Prisu okkas

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
Qli Fwuve pablirx nife

Xcode rename view
Kfilo sahora xioz

Xcode real-time renaming
Hqixi geos-cehe zecuyipw

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
Ltu whusipam dipo fok vew lxuhfez erfaj fajegaqn

The search & replace options
Nxi zeujpf & cossuze ihmiugv

The search results
Yqa soejgg biceldc

The results list allows you to verify each match
Rbu vuxobkw koyk uyrudx rie vo vuqegg aefc solzh

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.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now