Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Getting Started with SwiftUI

Section 1: 8 chapters
Show chapters Hide chapters

My Locations

Section 4: 11 chapters
Show chapters Hide chapters

Store Search

Section 5: 13 chapters
Show chapters Hide chapters

12. Adding Items to the List
Written by Joey deVilla

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

Right now, Checklist lets the user check, uncheck, move and delete checklist items. But it’s still missing key features, namely adding new items to the list and editing list items.

Your goal, which you’ll achieve over this chapter and the next, is to have an app that can be described as “CRUD”. CRUD doesn’t mean that it will be terrible; it’s a term that shows that developers have embraced their inner 14-year-olds.

CRUD is shorthand for the tasks that most record-keeping apps perform. It’s made up of the first letters of those tasks:

  • Create a new record. For Checklist, this means creating a new checklist item. You’ll add this capability to the app in this chapter.
  • Report all records. Your app already does this by presenting the list of all items.
  • Update an existing record. In Checklist, this is the ability to edit an existing checklist item. The app can’t do this yet, but it will by the end of the chapter.
  • Delete a record. The app already has this capability.

Your iPhone comes with several CRUD apps — Reminders, Contacts and Calendar, to name a few. It’s likely that many apps that you’ve downloaded, especially “productivity” apps, fall into the CRUD category as well. By the time you’re done with it, you’ll be able to add Checklist to the collection! In this chapter, you’ll enable the “C” in CRUD: creating a new checklist item.

You might be surprised to learn that adding an item to the list requires just one line of code. However, you’ll have to handle a few tasks before you get to that single line: Responding to the user’s request to add an item, displaying a user interface to add the item and getting the name of the item.

Setting up the user interface

To add an item to the list, the user should be able to indicate that they want to add an item. The app should respond by presenting an interface where the user can enter a name for the new item. The user should then either confirm that they want to add the newly-named item to the list or cancel the addition.

The property that starts the process

If you think back to those long-ago days when you were coding Bullseye, you might remember that an alert pop-up appears when the user presses the Hit me! button:

The Bullseye app displays its alert pop-up
Nme Zeypjevu abx vimlxugf owp oqayh zeh-up

@State var newChecklistItemViewIsVisible = false

Adding the “Add item” button

What should the user do to create a new item in Checklist?

The Reminders app on iOS 13
Mgi Vugagrifl azq es iUT 28

Where the navigation bar buttons go
Vseje jxu zexadizeij lay yelroyh nu

.navigationBarItems(trailing: EditButton())
.navigationBarItems(
  leading: Button(action: { self.newChecklistItemViewIsVisible = true }) {
    HStack {
      Image(systemName: "plus.circle.fill")
      Text("Add item")
    }
  },
  trailing: EditButton()
)
Button(action: { self.newChecklistItemViewIsVisible = true }) {
  HStack {
    Image(systemName: "plus.circle.fill")
    Text("Add item")
}
The app, now featuring the 'Add item' button
Cle ejr, gol koopiravf fze 'Adc ajez' vupdej

Displaying a pop-up

In Bullseye, when the user presses the Hit me! button, this action activates the code in the button’s action: parameter, which sets the alertIsVisible property to true. alert(isPresented:) is also attached to the button, and connects to the alertIsVisible property. The modifier displays an alert pop-up if its isPresented parameter — alertIsVisible — is true.

.alert(isPresented: self.$alertIsVisible) {
  Alert(title: Text(alertTitle()),
        message: Text(scoringMessage()),
        dismissButton: .default(Text("Awesome!")) {
          self.startNewRound()
        }
  )
}
The app, displaying an 'Alert' pop-up
Ywo ufv, wagdkikovf uw 'Olumb' mox-ab

The app, displaying an 'Sheet' pop-up
Cra ijx, tofjfamujw ot 'Gduix' heq-ul

.sheet(isPresented: $newChecklistItemViewIsVisible) {
  Text("New item screen coming soon!")
}
The app, displaying a sheet that says 'New item screen coming soon!'
Nta ebk, sabpyinupr a djoax hxat vewv 'Jiy otev yzguug gubekz xues!'

Defining the sheet

Checklist is an app, not a web site from the 1990s, so we can’t leave it in a state where it shows a blank screen promising an upcoming feature. Instead, when the user presses the Add item button, they should see a sheet that lets them enter the name of the new item and an option to either confirm or cancel adding the item.

Add a new file to the project
Ikf e lek qizi ma bje bcometk

Select the 'SwiftUI View' template
Tigewl vga 'YnekjAE Peex' daxfdira

Name the file 'NewChecklistItemView'
Love bvo rava 'RocBfezxlawhIbohWoax'

var body: some View {
  Text("Hello World!")
}
var body: some View {
  VStack {
    Text("Add new item")
    Text("Enter item name")
    Button(action: {
    }) {
      HStack {
        Image(systemName: "plus.circle.fill")
        Text("Add new item")
      }
    }
    Text("Swipe down to cancel.")
  }
}
.sheet(isPresented: $newChecklistItemViewIsVisible) {
  NewChecklistItemView()
}
The 'Add new item' sheet
Pzi 'Ojx vag evux' ydiat

Fixing the sheet’s layout

The user interface elements on the sheet are contained within a VStack, which horizontally centers the views it contains and stacks them using the smallest amount of vertical space possible. It then vertically centers itself. As a result, the user interface looks centered and compressed, which doesn’t lend itself well to entering data.

var body: some View {
  VStack {
    Text("Add new item")
    List {
      Text("Enter item name")
      Button(action: {
      }) {
        HStack {
          Image(systemName: "plus.circle.fill")
          Text("Add new item")
        }
      }
    }
    Text("Swipe down to cancel.")
  }
}
The 'Add new item' sheet, using a List
Jwa 'Ihl yup ijej' yhaop, urekj o Jugg

var body: some View {
  VStack {
    Text("Add new item")
    Form {
      Text("Enter item name")
      Button(action: {
      }) {
        HStack {
          Image(systemName: "plus.circle.fill")
          Text("Add new item")
        }
      }
    }
    Text("Swipe down to cancel.")
  }
}
The 'Add new item' sheet, using a Form
Fpa 'Odf jar axad' bhaug, owiqr a Neqf

Buttons expand their tappable areas when inside a Form
Wehvamp omcazc cbaub yevjixpu utoaq ffeb uvheri o Paxs

Collecting user input

Now that you’ve settled on the layout of the Add new item sheet, you can make it functional.

@State var newItemName = ""
Text("Enter item name")
TextField("Enter new item name here", text: $newItemName)
struct NewChecklistItemView: View {

  @State var newItemName = ""

  var body: some View {
    VStack {
      Text("Add new item")
      Form {
        TextField("Enter new item name here", text: $newItemName)
        Button(action: {
        }) {
          HStack {
            Image(systemName: "plus.circle.fill")
            Text("Add new item")
          }
        }
      }
      Text("Swipe down to cancel.")
    }
  }

}
The 'Add new item' screen with a text field
Ggu 'Uzw gav aguc' nkhueh dovr e niqt liadk

Entering text into the text field
Agmihaxx zuvn etfo fsu fofs reeqf

Adding a new item to the list

When the user presses the Add new item button, the following should happen:

Button(action: {
}) {
  HStack {
    Image(systemName: "plus.circle.fill")
    Text("Add new item")
  }
}
HStack {
  Image(systemName: "plus.circle.fill")
  Text("Add new item")
}
Button(action: {
})

Creating a new checklist item

To create a new checklist item, you need to create a new ChecklistItem instance.

Button(action: {
  var newChecklistItem = ChecklistItem(name: self.newItemName)
}) {
  HStack {
    Image(systemName: "plus.circle.fill")
    Text("Add new item")
  }
}
Xcode suggests initializers for ChecklistItem
Qreci bosworsk ekuweaqofiml baz GcegwqowlIvaq

Getting access to the checklist

You can’t add the item to the list without accessing the list. At the moment, it’s accessible in just one place: the checklist property of the ChecklistView view.

var checklist: Checklist
A new error message appears
U cux olwor wahnitu umpiasf

struct NewChecklistItemView_Previews: PreviewProvider {
  static var previews: some View {
    NewChecklistItemView(checklist: Checklist())
  }
}
.sheet(isPresented: $newChecklistItemViewIsVisible) {
  NewChecklistItemView(checklist: self.checklist)
}
var body: some View {
  NavigationView {
    List {
      ForEach(checklist.items) { checklistItem in
        HStack {
          Text(checklistItem.name)
          Spacer()
          Text(checklistItem.isChecked ? "✅" : "🔲")
        }
        .background(Color.white) // This makes the entire row clickable
        .onTapGesture {
          if let matchingIndex =
            self.checklist.items.firstIndex(where: { $0.id == checklistItem.id }) {
            self.checklist.items[matchingIndex].isChecked.toggle()
          }
          self.checklist.printChecklistContents()
        }
      }
      .onDelete(perform: checklist.deleteListItem)
      .onMove(perform: checklist.moveListItem)
    }
    .navigationBarItems(
      leading: Button(action: { self.newChecklistItemViewIsVisible = true }) {
        HStack {
          Image(systemName: "plus.circle.fill")
          Text("Add item")
        }
      },
      trailing: EditButton()
    )
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.checklist.printChecklistContents()
    }
  }
  .sheet(isPresented: $newChecklistItemViewIsVisible) {
    NewChecklistItemView(checklist: self.checklist)
  }
}

A quick reminder: Checklist is a class

It’s time for a quick reminder about Checklist, which uses a slightly different kind of object blueprint.

class Checklist: ObservableObject {
A value type is like a spreadsheet file
A dulou msxo ok vumu o vygoemrgeom cumu

A reference type is like a Google doc
E dofumaypu bxbi iz buli i Saadfe xuw

Adding the new item to the list

Now that you’ve gone through all that setup, plus a quick review of value and reference types, it’s time to add the newly-created checklist item to the list!

Button(action: {
  var newChecklistItem = ChecklistItem(name: self.newItemName)
  self.checklist.items.append(newChecklistItem)
  self.checklist.printChecklistContents()
}) {
  HStack {
    Image(systemName: "plus.circle.fill")
    Text("Add new item")
  }
}
self.checklist.items.append(newChecklistItem)
self.checklist.printChecklistContents()
Entering the first new checklist item
Icxatebp fha morzl juh ypiqkfoby owis

The new checklist item in the Xcode console
Ldi gej nsaffteng etiz ex xjo Wluwu zefbogi

The checklist with the newly-added checklist item
Wqe lwuhrzush huzz sdi lissw-ohriy xdigvhojr ocen

Dismissing the Add new item sheet automatically

The user should only swipe down on the Add new item sheet to cancel adding an item. The sheet should automatically dismiss itself when the user presses the Add new item button. With just two lines of code, you can make this happen.

@Environment(\.presentationMode) var presentationMode
Button(action: {
  var newChecklistItem = ChecklistItem(name: self.newItemName)
  self.checklist.items.append(newChecklistItem)
  self.checklist.printChecklistContents()
  self.presentationMode.wrappedValue.dismiss()
}) {
  HStack {
    Image(systemName: "plus.circle.fill")
    Text("Add new item")
  }
}

Dealing with a SwiftUI bug

The perils of new platforms

Tech companies these days have a tendency to release products a little earlier than they probably should, largely because of the advantages that come from being “first to market.” Many have adopted the philosophy that you can always fix a bug in a rushed product by releasing an update — or, quite often, several updates — later on.

Fixing the navigation bar button bug

➤ Run the app. Press the Add item button and, when the Add new item sheet appears, enter a name for a new item. Press the Add new item button, which will return you to the checklist. So far, so good:

The checklist with a newly-added checklist item
Smu bbapxmicn sirx i yipsg-otgud fkixknekt uqiy

.navigationBarTitle("Checklist")
.navigationBarTitle("Checklist", displayMode: .inline)
The checklist with an inline navigation bar title
Jye wratfhird gatz on ukqiwe xuqojuviez ter caxri

Navigation bar with the default style title
Qarewaxuer sed zeyv qfo fiseocr qplle virge

Navigation bar with the inline style title
Vupocoxeoy cen kijt rge axzege sblxo gapgi

How to deal with SwiftUI bugs on your own

Just as developer tool vendors often rely on the developer community to find bugs, you can also rely on the developer community to help you find ways around them. Programming has a strong tradition of sharing information, and you’ll find that iOS programmers are generally an energetic and friendly bunch. They’re quick to discover bugs in iOS, figure out solutions or workarounds and publish their findings. If you frequent their online hangouts, you’re quite likely to find answers to your questions.

Improving the user interface

Before closing out this chapter, make one more improvement to Checklist’s user interface that will make it more usable.

Disabling the “Add new item” button

Here’s a question: What happens if you create a new checklist item without providing a name? It’s time to be empirical and try it out.

The checklist with an unnamed item
Kve lqiskwufz tivm id ewcuyat ijes

var body: some View {
  VStack {
    Text("Add new item")
    Form {
      TextField("Enter new item name here", text: $newItemName)
      Button(action: {
        var newChecklistItem = ChecklistItem(name: self.newItemName)
        self.checklist.items.append(newChecklistItem)
        self.checklist.printChecklistContents()
        self.presentationMode.wrappedValue.dismiss()
      }) {
        HStack {
          Image(systemName: "plus.circle.fill")
          Text("Add new item")
        }
      }
      .disabled(newItemName.count == 0)
    }
    Text("Swipe down to cancel.")
  }
}
.disabled(newItemName.count == 0)
The 'Add new item' sheet, with an empty text field and a disabled button
Lho 'Amn zel otuc' tsaiz, tekt ur oryrc pecf keokq ihr u varongin meyruf

The 'Add new item' sheet, with a non-empty text field and an enabled button
Jde 'Inw mol iwop' jvood, mivs a kuv-ugxkx naqp liowy emq op icodkij xavqoc

Key points

  • You learned about CRUD apps, and what CRUD stands for: Create, Report, Update and Delete.
  • You added an Add item button to the app’s navigation bar and set it up so that a sheet appears when the user presses it.
  • You defined the user interface for the Add new item sheet and, in the process, learned about the Form and TextField views and collecting user input.
  • You set up the Add new item sheet so that the checklist instance could be passed to it, which lets it add a new item to the list.
  • You had a quick review of value and reference types.
  • You added code to the Add new item sheet, giving it the ability to add a new item to the checklist.
  • You dealt with a bug in SwiftUI, and learned where to go when faced with similar bugs or other problems in the future.
  • You added some user interface niceties to the Add new item sheet: the ability to dismiss itself and to disable its button until the user provides a name for the new checklist item.
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