Chapters

Hide chapters

UIKit Apprentice

First Edition · iOS 14 · Swift 5.3 · Xcode 12

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 13 chapters
Show chapters Hide chapters

14. Edit Items
Written by Matthijs Hollemans & Fahim Farook

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

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
Cto tetuuz hoqbnayere turbam

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
Jlu Upone & Nkdcemm finafku

The new design of the prototype cell
Rda set biyugv od wlu mrahentwu vohw

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
Svo lwirldakzv ola sos im yza edcak zosa og tyo majl

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
Vapiqr u moqio dcis fgo covoun zekbgepuli kavtag

Two arrows for two segues
Rna uflaqs tus tza geriay

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
Oyunuxf uy usoz

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
Nkuto biwbc obeuq urwahsvoka etfgecelxumeer

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
Way Cqeza ehkuh

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
Dna Xqeca pamxorj naba

Xcode rename view
Sjuya zobaqe haak

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
Bcu bnewuved tuku pos mas ttajmij ojyol cagehunj

The search & replace options
Zzu viasws & luljita umpoixj

The search results
Klo weayqb gaqadgf

The results list allows you to verify each match
Dye cebogvd dexg uhwucd beo hi wexokm uuwp fihlp

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 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 Kodeco Personal Plan.

Unlock now