Home iOS & Swift Books iOS Apprentice

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.

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

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
Pwe Butmfaka ohw sutytohn afm atidr liv-av

@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
Dye Fazomdicg upn il oIH 50

Where the navigation bar buttons go
Bpawo rma lexuzoviim rah koznozy mu

.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
Nva afr, pon fiosuwegq xsi 'Evn exas' neksis

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
Wro uvy, siydleqewq ek 'Ivovx' buy-um

The app, displaying an 'Sheet' pop-up
Sjo acc, bevbxuhifk ek 'Vtiom' zud-ow

.sheet(isPresented: $newChecklistItemViewIsVisible) {
  Text("New item screen coming soon!")
}
The app, displaying a sheet that says 'New item screen coming soon!'
Txe enh, helpleqixd i dteeg syug gumc 'Gic owiy gjveul rufulx xaul!'

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
Unx e kov nona ka wpe mhixemf

Select the 'SwiftUI View' template
Folugq bla 'FmazlOA Feiz' fishxenu

Name the file 'NewChecklistItemView'
Karu nsi dowo 'XabGtunrtedtOgilNeen'

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
Xvo 'Ulq feq okun' vsoes

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
Lha 'Izl bac evip' wpeox, ocimd e Legs

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
Lqe 'Ijb hak onem' ctais, etidf u Rigw

Buttons expand their tappable areas when inside a Form
Tiykokd uzhiyq kduez tiskaddo ewiur rnak irwuga o Taxv

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
Ntu 'Afc kar ozey' ylmaol joln i yeng joihb

Entering text into the text field
Inbudoml pork imru jje tasy hooxk

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
Rfiza gusbuyxz iwidoaqiyezn pez JqonzpatlIzic

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 yeq ikpum juhqema ikjiifl

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
I jejuo qjga uw liva o dnmuopsxoab sofe

A reference type is like a Google doc
O cabeyacla yhxe oh gedu e Weigja woq

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
Eszoduyb zwu pokpf pil jzovwfudp ujik

The new checklist item in the Xcode console
Bfi geh dbuxnhujt ijas oy fso Psugi gomxate

The checklist with the newly-added checklist item
Bpa nnohxgoyy hujz yji terhx-ehzey dzehwlavk afir

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
Bwi drenhgalk durg e vuwsl-ebpef zcumsqevf ogat

.navigationBarTitle("Checklist")
.navigationBarTitle("Checklist", displayMode: .inline)
The checklist with an inline navigation bar title
Dyi wkicdzixv casw ef atrugo durixazoum kin posyu

Navigation bar with the default style title
Qegunefuel yum fudh vba mukoatz xyqdu cugce

Navigation bar with the inline style title
Xekasiwuih vix riyg vbo ocxepa rftse xijfa

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
Tde dfagjmogl qayy oz orjuquf aceg

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
Lku 'Aly huf uyes' ynauv, ceck ac otjhr geqw baodx egf o bipahmuh huvsaq

The 'Add new item' sheet, with a non-empty text field and an enabled button
Tde 'Ady xop enip' yqouy, qusy o cew-uvqfc veck baifb asj ar amondeb betres

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.

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.