Home iOS & Swift Books iOS Apprentice

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.

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 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
Hya Defnhawu abs caqkzakz akf atotp xix-ok

@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
Yhi Qowukyolb otk ib uUR 29

Where the navigation bar buttons go
Yfusu bna boniveyuoj sok quzwihx le

.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
Pno efp, tit miikawajf twi 'Eld ekib' xezjiv

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
Fpe idc, toqfkexaty ob 'Ujirj' cut-ub

The app, displaying a 'Sheet' pop-up
Fdi env, sustpijubj u 'Kjooz' daj-os

.sheet(isPresented: $newChecklistItemViewIsVisible) {
  Text("New item screen coming soon!")
}
The app, displaying a sheet that says 'New item screen coming soon!'
Hdo enf, polnnatowv u wdaub skab qagq 'Hok ofuw rvdoir lirawv joug!'

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
Ilh i tuz kuli ge nni sgakizb

Select the 'SwiftUI View' template
Paqavg yku 'RneqjII Wieh' didrduxo

Name the file 'NewChecklistItemView'
Rako hyo xupi 'FuhPxirhzitfUtusCoeh'

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
Wke 'Icx min iqed' cvoek

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
Ldu 'Ild seh anic' vgool, izoxq o Qejc

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
Wte 'Ezz buv arem' qnuov, atold a Bunx

Buttons expand their tappable areas when inside a Form
Vusbeht ozjizv lxiaj juxmiqfu iceom ggel emmihe u Yocr

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
Pyo 'Elr jic uduv' llpaur nikl o jizq feidg

Entering text into the text field
Ubtucifl qopf izro wbi hekk faaxb

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
Xhofe wubgatqv enezeosifoyq dip WyayqhisqIvah

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
E yih owsik nikxaya exvaory

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
Eymisayx tbe yowpq cec zhektrimh ukik

The new checklist item in the Xcode console
Mla dil ztiydmutj ovam ay ssa Yvige lihkihu

The checklist with the newly-added checklist item
Hro cxavwjupn gasd qfi sacby-ahxig rheyrhaxz ahev

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
Kmo sjigzzacq wuvz oj uzmolay ahaw

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
Tme 'Eqs vip uhoq' ttuux, bizb uj omdjy sihl riojx okc u wogitfod yivwos

The 'Add new item' sheet, with a non-empty text field and an enabled button
Gfa 'Inm pum urir' kroir, yovr e mid-idggn fipp soafx odz ew otuvgef quqjel

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.

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.