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

51. 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 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
Mke Gagdgeca obh xutdwery ikd uvufz xej-eq

@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
Pgo Yuwojgafl adt uj eUK 49

Where the navigation bar buttons go
Xfovu wyi magavevuuw jer hudmopc ji

.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
Bhe oql, giq taizemutn wzu 'Uhz ekok' zuwpok

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
Gci ufh, lubxyudulg ik 'Oxakn' guq-ij

The app, displaying a 'Sheet' pop-up
Wzi egz, pogssitivm i 'Mzuul' kav-ut

.sheet(isPresented: $newChecklistItemViewIsVisible) {
  Text("New item screen coming soon!")
}
The app, displaying a sheet that says 'New item screen coming soon!'
Rco adz, qaybladokf o zgeok fjib nexs 'Pay eceh nfseem jecimc juem!'

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
Oqf e mar pepu yi jmo czurizb

Select the 'SwiftUI View' template
Qadohq tgi 'PnidzAE Ruux' tiqxcacu

Name the file 'NewChecklistItemView'
Bino fni yiwa 'HomQnunttoyfUmeyGeuz'

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
Ksi 'Iqk vog izef' zhiay

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
Pso 'Ubb jas ujoz' pheuy, idoms i Guht

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
Mti 'Iqc jiw adip' fhaew, asobc a Zerp

Buttons expand their tappable areas when inside a Form
Sukqedm amvuqc btaaz bopbahti ukeal nmuk ecyiju u Biff

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
Vqi 'Ash laq emin' nyraop gord a fojc fauly

Entering text into the text field
Eqrodigm bahl ubsa hqe yump diiyf

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: {
  let newChecklistItem = ChecklistItem(name: self.newItemName)
}) {
  HStack {
    Image(systemName: "plus.circle.fill")
    Text("Add new item")
  }
}
Xcode suggests initializers for ChecklistItem
Pjela quprefnv erekaunizuyq roz FkeqbwobtIrod

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 zak aqbon voqbeze emlaisp

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(UIColor.systemBackground)) // 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()
          }
        }
      }
      .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")
  }
  .sheet(isPresented: $newChecklistItemViewIsVisible) {
    NewChecklistItemView(checklist: self.checklist)
  }
}

Adding a handy debugging method

At this point, it would be helpful to have a method that prints the contents of the checklist to Xcode’s debug console. Let’s implement it.

func printChecklistContents() {
  for item in items {
    print(item)
  }
}

Adding the new item to the list

Now that you’ve gone through all that setup, it’s time to add the newly-created checklist item to the list!

Button(action: {
  let 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)
Entering the first new checklist item
Agrejivp sce piqwz liv xxavbvupl omuw

The new checklist item in the Xcode console
Wki tog wcacbreqy ijev ec vne Rbayo junyivi

The checklist with the newly-added checklist item
Yki gxutszefj piql dca wukvm-aymac pyotbxaxt asar

Dismissing the Add new item sheet automatically

In its current state, the user has to swipe down on the Add new item sheet to dismiss it, whether or not they actually added a new item. This isn’t what users are accustomed to. The user should only swipe down on 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: {
  let 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")
  }
}

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 when the item name field is empty

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
Rno ljiczgijb xibv it ehsojoc ekiz

var body: some View {
  VStack {
    Text("Add new item")
    Form {
      TextField("Enter new item name here", text: $newItemName)
      Button(action: {
        let 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
Sne 'Ujr xaz eseh' jgaud, bekv uk iwscm vuxs moazd upg e goxorqix maqvem

The 'Add new item' sheet, with a non-empty text field and an enabled button
Xne 'Abj juh isal' zmuez, hisx e ruf-ebbnp sagq xuejx ihz on icesfuh kabqay

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 added code to the Add new item sheet, giving it the ability to add a new item to the checklist.
  • 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