Home iOS & Swift Books iOS Apprentice

9
List Views 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.

Ready to get started on your next app? Here you go!

To-do list apps are one of the most popular types of app on the App Store. Do a search on the web for “iOS to-do list apps” and you’ll see reviews of dozens of list apps, even though iOS comes bundled with the Reminders app. From pilots to busy parents to surgeons to Santa Claus, many people need to have a good checklist.

Building a to-do list app is a rite of passage for budding iOS developers. It’s the kind of app that’s so useful that it has its own category. Making one will teach you to master code features and functionality that you’ll end up using in so many other apps. It makes sense for you to create one as well.

Your own to-do list app, Checklist, will look like this when you’re finished:

The finished Checklist app
The finished Checklist app

This app lets you organize to-do items into a list with various priority levels. You can check off the items once you’ve completed them.

As far as to-do list apps go, Checklist is very basic, but don’t let that fool you. Even a simple app such as this has a lot of complexity behind the scenes. In building it, you’ll learn many different programming concepts. You’re going to continue to use SwiftUI, the brand new way to build iOS apps, to build Checklist.

This chapter will cover:

  • NavigationView and List views: You’ll quickly review NavigationView, then you’ll learn to use List. You’ll also see how both views are often used together in apps that you probably use every day. Finally, you’ll build an app that displays a basic list.
  • Arrays: Just as Lists organize arrange views in a row, arrays organize data in the same way. Arrays are so useful that they’re the most common data structure that programmers use.
  • Loops: One of the reasons that computers are so useful is that they’re very good at doing the same thing over and over again, no matter how tedious it is. How do they do this? With loops. You’ll learn how to make your first dynamic list using List, arrays and loops.
  • Deleting items from the list: At this point, you’ve filled the onscreen list with items. You’ll then learn how to give the user the power to delete any item with a swipe of their finger.
  • Moving list items: Deleting items is pretty cool, but giving the user the ability to rearrange the items on the list is even more impressive. It’s amazing how few lines of code this takes.
  • Key points: A quick review of what you’ve learned in this chapter.

NavigationView and List views

In the previous app, Bullseye, you learned to use NavigationView. NavigationView acts as a container for screens and makes it possible to navigate between them by creating a navigation hierarchy that’s similar to how you navigate the web.

Embedding Bullseye’s main screen, ContentView, inside a NavigationView lets users navigate to other screens by stacking those screens on top of ContentView. You did this by using a NavigationLink as the Info button. When a user presses the NavigationLink, that creates an instance of AboutView, which is then stacked on top of ContentView. Since AboutView is on top of the stack contained within NavigationView, the user sees that screen:

A basic list with five 'to-do' items
A basic list with five 'to-do' items

When the stack within a NavigationView is two or more screens deep, the navigation bar displays a “Back” button with the title of the screen just below. With Bullseye, the “Back” button that appears on AboutView looks like this: 🎯 Bullseye 🎯. Pressing the “Back” button removes AboutView from the stack of screens within the NavigationView, which makes ContentView visible again:

A basic list with five 'to-do' items
A basic list with five 'to-do' items

List views

As its name implies, a List view displays lists, which are rows of data arranged in a single column. This user interface element is extremely versatile, the most important one to master in iOS development.

A basic list with five 'to-do' items
O qahul hipx yugn reca 'wo-wi' eyeyc

A basic list

Start with a static list, which is one where the items and their order don’t change. You might use this sort of list when you want to present the user with several choices or a table of contents.

var body: some View {
  Text("Hello World")
}
var body: some View {
  List {
    Text("Walk the dog")
    Text("Brush my teeth")
    Text("Learn iOS development")
    Text("Soccer practice")
    Text("Eat ice cream")
  }
}
A basic list with five 'to-do' items
E qecow lavd gutg peno 'fo-wa' aladc

var body: some View {
  NavigationView {
    List {
      Text("Walk the dog")
      Text("Brush my teeth")
      Text("Learn iOS development")
      Text("Soccer practice")
      Text("Eat ice cream")
    }
  }
}
The list, now contained within a navigation view
Cnu gufy, lor litcauhuy kuxkac u civoxeraoy yeek

var body: some View {
  NavigationView {
    List {
      Text("Walk the dog")
      Text("Brush my teeth")
      Text("Learn iOS development")
      Text("Soccer practice")
      Text("Eat ice cream")
    }
    .navigationBarTitle("Checklist")
  }
}
The app with a title in the navigation bar
Lwo ojm mash i kiyxu ug hvi qawujedeoz mub

Lists with sections

There are two styles of List: plain and grouped. Right now, the app’s List uses the plain style.

var body: some View {
  NavigationView {
    List {
      Section(header: Text("High priority")) {
        Text("Walk the dog")
        Text("Brush my teeth")
        Text("Learn iOS development")
      }
      Section(header: Text("Low priority")) {
        Text("Soccer practice")
        Text("Eat ice cream")
      }
    }
    .listStyle(GroupedListStyle())
    .navigationBarTitle("Checklist")
  }
}
The app with a grouped style list
Wdo ogn becn e kgiidat ybvbu gumf

The limits of views

Most people who use checklists have more than just five to-do items. At this point, make a more realistic list by adding more items so that both the high-priority and low-priority groups each have ten to-do items.

var body: some View {
  NavigationView {
    List {
      Section(header: Text("High priority")) {
        Text("Walk the dog")
        Text("Brush my teeth")
        Text("Learn iOS development")
        Text("Make dinner")
        Text("Do laundry")
        Text("Pay bills")
        Text("Finish homework")
        Text("Change internet provider")
        Text("Read Raywenderlich.com")
        Text("Clean the kitchen")
      }
      Section(header: Text("Low priority")) {
        Text("Soccer practice")
        Text("Eat ice cream")
        Text("Take vocal lessons")
        Text("Record hit single")
        Text("Learn every martial art")
        Text("Design costume")
        Text("Design crime-fighting vehicle")
        Text("Come up with superhero name")
        Text("Befriend space raccoon")
        Text("Save the world")
      }
    }
    .listStyle(GroupedListStyle())
    .navigationBarTitle("Checklist")
  }
}
The list with ten items in each section
Jzu deks qops xig atosj ez uerl vujyeun

var body: some View {
  NavigationView {
    List {
      Section(header: Text("High priority")) {
        Text("Walk the dog")
        Text("Brush my teeth")
        Text("Learn iOS development")
        Text("Make dinner")
        Text("Do laundry")
        Text("Pay bills")
        Text("Finish homework")
        Text("Change internet provider")
        Text("Read RayWenderlich.com")
        Text("Clean the kitchen")
        Text("Wash the car")
      }
      Section(header: Text("Low priority")) {
        Text("Soccer practice")
        Text("Eat ice cream")
        Text("Take vocal lessons")
        Text("Record hit single")
        Text("Learn every martial art")
        Text("Design costume")
        Text("Design crime-fighting vehicle")
        Text("Come up with superhero name")
        Text("Befriend space raccoon")
        Text("Save the world")
      }
    }
    .listStyle(GroupedListStyle())
    .navigationBarTitle("Checklist")
  }
}
The 'Ambiguous reference' error message
Hle 'Ixyevauim peqoxemla' ayyiy cumlaga

var body: some View {
  NavigationView {
    List {
      Section(header: Text("High priority")) {
        Group {
          Text("Walk the dog")
          Text("Brush my teeth")
          Text("Learn iOS development")
          Text("Make dinner")
          Text("Do laundry")
          Text("Pay bills")
        }
        Group {
          Text("Finish homework")
          Text("Change internet provider")
          Text("Read RayWenderlich.com")
          Text("Clean the kitchen")
          Text("Wash the car")
        }
      }
      Section(header: Text("Low priority")) {
        Text("Soccer practice")
        Text("Eat ice cream")
        Text("Take vocal lessons")
        Text("Record hit single")
        Text("Learn every martial art")
        Text("Design costume")
        Text("Design crime-fighting vehicle")
        Text("Come up with superhero name")
        Text("Befriend space raccoon")
        Text("Save the world")
      }
    }
    .listStyle(GroupedListStyle())
    .navigationBarTitle("Checklist")
  }
}

The limits of static lists

Static lists have their uses, but they’re missing a lot of features that a checklist app needs. The user needs to be able to add and delete items from the list, edit existing items and change their order in the list.

Arrays

Up to this point, you’ve been working with scalar variables and constants. These are variables and constants that hold one value at any given time. You used several scalar variables and constants in the Bullseye app. Each one acted as a container for one value, such as the score, the round, the value of the target and the message that the user received based on their score.

Scalar values and array values
Rluxec vuduab ikw iprip yozauw

Creating an array

Your next step is to create an array to hold the checklist items. Enter the following, immediately after the start of ContentView (the struct ContentView: View { line):

var checklistItems = ["Walk the dog", "Brush my teeth", "Learn iOS development", "Soccer practice", "Eat ice cream"]

Accessing array elements

The name checklistItems refers to the entire array. When you need to provide the entire array to an object or method, as you’ll need to do shortly, you’ll use that.

checklistItems[2]
var body: some View {
  NavigationView {
    List {
      Text(checklistItems[0])
      Text(checklistItems[1])
      Text(checklistItems[2])
      Text(checklistItems[3])
      Text(checklistItems[4])
    }
    .navigationBarTitle("Checklist")
  }
}
struct ContentView: View {
  
  var checklistItems = ["Walk the dog", "Brush my teeth", "Learn iOS development", "Soccer practice", "Eat ice cream"]

  var body: some View {
    NavigationView {
      List {
        Text(checklistItems[0])
        Text(checklistItems[1])
        Text(checklistItems[2])
        Text(checklistItems[3])
        Text(checklistItems[4])
      }
      .navigationBarTitle("Checklist")
    }
  }
}
The list, using an array
Qna kobq, uhafw aj alnuz

Changing array elements and responding to taps on list items

Changing an array element is simple: You put the array element you want to change on the left side of an = and the value you want to change it to on the right side. For example, to change the contents of the first item in checklistItems to “Take the dog to the vet”, you’d use this code:

checklistItems[0] = "Take the dog to the vet"
var body: some View {
  NavigationView {
    List {
      Text(checklistItems[0])
        .onTapGesture {
          self.checklistItems[0] = "Take the dog to the vet"
        }
      Text(checklistItems[1])
      Text(checklistItems[2])
      Text(checklistItems[3])
      Text(checklistItems[4])
    }
    .navigationBarTitle("Checklist")
  }
}
The ’self is immutable’ error message
Vzu ’tihj ub okhawudji’ urgew vabnayo

@State var checklistItems = [
  "Walk the dog",
  "Brush my teeth",
  "Learn iOS development",
  "Soccer practice",
  "Eat ice cream",
]
The list, with the first item changed to ’Take the dog to the vet’ after the user tapped on it
Gge kojd, pagg tja qicqq uyuq zyicpav sa ’Qaqu xli vux la wka dul’ oshex pmi ifuc yujjab iq os

The limits of the current approach

Suppose you added an additional item — “Learn every martial art” — to checklistItems:

@State var checklistItems = [
  "Walk the dog",
  "Brush my teeth",
  "Learn iOS development",
  "Soccer practice",
  "Eat ice cream",
  "Learn every martial art",
]
List {
  Text(checklistItems[0])
    .onTapGesture {
      self.checklistItems[0] = "Take the dog to the vet"
    }
  Text(checklistItems[1])
  Text(checklistItems[2])
  Text(checklistItems[3])
  Text(checklistItems[4])
}

Loops

Up to this point, you’ve experienced two different kinds of flow control, or ways of executing your code. Let’s look at them, and then you’ll learn a new kind of flow control: Looping.

Flow control

The first kind of flow control that you’ve seen is sequence, which is simply performing instructions in the order in which they appear.

func startNewGame() {
  score = 0
  round = 1
  resetSliderAndTarget()
}
func alertTitle() -> String {
  let title: String
  if sliderTargetDifference == 0 {
    title = "Perfect!"
  } else if sliderTargetDifference < 5 {
    title = "You almost had it!"
  } else if sliderTargetDifference <= 10 {
    title = "Not bad."
  } else {
    title = "Are you even trying?"
  }
  return title
}

for loops

You’ll start by displaying all the elements in checklistItems in Xcode’s debug console. Another way of saying this is “For every item in checklistItems, print its name,” which is pretty close to the way you’d code it in Swift:

for item in checklistItems {
  print(item)
}
@State var checklistItems = [
  "Walk the dog",
  "Brush my teeth",
  "Learn iOS development",
  "Soccer practice",
  "Eat ice cream",
]
func printChecklistContents() {
  for item in checklistItems {
    print(item)
  }
}
var body: some View {
  NavigationView {
    List {
      Text("Nothing to see here...yet!")
    }
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.printChecklistContents()
    }
  }
}
struct ContentView: View {
  
  @State var checklistItems = [
    "Walk the dog",
    "Brush my teeth",
    "Learn iOS development",
    "Soccer practice",
    "Eat ice cream",
  ]
  var body: some View {
    NavigationView {
      List {
        Text("Nothing to see here...yet!")
      }
      .navigationBarTitle("Checklist")
      .onAppear() {
        self.printChecklistContents()
      }
    }
  }
  func printChecklistContents() {
    for item in checklistItems {
      print(item)
    }
  }

}
The Xcode window, with the contents of checklistItems displayed in the debug console
Bwi Gpetu veswap, wazr tqu fabmezjt as pqojdcaxhAhuqj wawzgakiv ed pde xobif fehpisi

The ForEach view

SwiftUI has a view called ForEach that takes a collection of data, such as an array, and generates views based on that data. This is one of those cases where “Show, don’t tell” is the better approach, so take a look at ForEach in action first, then you’ll look at it in more detail afterward.

var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
      }
    }
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.printChecklistContents()
    }
  }
}
The original five-element array displayed using ’ForEach’
Kno uyategiw kolu-upojofm erfaq xagfloned uborx ’NayUutl’

@State var checklistItems = [
  "Take vocal lessons",
  "Record hit single",
  "Learn every martial art",
  "Design costume",
  "Design crime-fighting vehicle",
  "Come up with superhero name",
  "Befriend space raccoon",
  "Save the world",
  "Star in blockbuster movie",
]
A completely different list displayed using ’ForEach’
U kaslsubodg guznifevv nacw fuklzokus ewuwr ’JuhUukq’

ForEach(checklistItems, id: \.self) { item in
  Text(item)
}
A completely different list displayed using ’ForEach’
E zonkranepd yibhodedq sady xizghejuw uyefk ’XotOurk’

The similarities between ’for’ and ’ForEach’
Nva fezahevipaif yuqzaip ’noh’ obp ’DibAuqw’

Deleting items from the list

Now that the list is based on an array and is no longer a “hardwired” part of the user interface, changing the array’s contents can change the list’s contents.

Adding items to the end of an array (and the checklist)

Since arrays are ordered lists of items, and lists often grow by adding items to the end, arrays have an append() method, which lets you add new items to the end of the array.

append("One more item!!!")
@State var checklistItems = [
  "Walk the dog",
  "Brush my teeth",
  "Learn iOS development",
  "Soccer practice",
  "Eat ice cream",
]
var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
          .onTapGesture {
            self.checklistItems.append(item)
            self.printChecklistContents()
          }
      }
    }
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.printChecklistContents()
    }
  }
}
ForEach(checklistItems, id: \.self) { item in
  Text(item)
    .onTapGesture {
      self.checklistItems.append(item)
      self.printChecklistContents()
    }
}
Adding a new item to the end of the list
Adkenx o xon orix ma lmu oyt ay lwa hahv

Removing items from an array (and the checklist)

There are several methods that remove an element from an array. You’ll try two of them out in your app.

Removing items using remove(at:)

The simplest one is remove(at:), which removes the element at the given index. For example, if you wanted to remove the first element of checklistItems, you’d use the code checklistItems.remove(at: 0).

self.checklistItems.append(item)
self.checklistItems.remove(at: 0)
var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
          .onTapGesture {
            self.checklistItems.remove(at: 0)
            self.printChecklistContents()
          }
      }
    }
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.printChecklistContents()
    }
  }
}
An empty checklist
Ac amrjh qkehtxamh

Using remove(atOffsets:) to remove list items

Another way to remove list items is remove(atOffsets:). This is a bulk version of remove(at:), which removes a specific range of elements from an array. You specify the starting index and ending index of the items you want to remove using IndexSet. To see it in action, change the app so that tapping a list item removes the elements from index 0 through index 4. You do this by replacing this line in the onTapGesture() method:

self.checklistItems.remove(at: 0)
let indexesToRemove = IndexSet(integersIn: 0...4)
self.checklistItems.remove(atOffsets: indexesToRemove)
var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
          .onTapGesture {
            let indexesToRemove = IndexSet(integersIn: 0...4)
            self.checklistItems.remove(atOffsets: indexesToRemove)
            self.printChecklistContents()
          }
      }
    }
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.printChecklistContents()
    }
  }
}

Responding to the “swipe to delete” gesture

Even though many apps use the list control that comes standard with iOS, many people still don’t know the standard “swipe to delete” gesture.

The ’Delete’ button that appears when you ’slide to delete’ a list item
Thu ’Sihazi’ bogjel yben usxuaxd wjuw mii ’fcita be vixici’ o bevg iwuz

The ’Delete’ button at full width
Pfu ’Paladi’ xixwoz uh bevh foqkt

func deleteListItem(whichElement: IndexSet) {
  checklistItems.remove(atOffsets: whichElement)
  printChecklistContents()
}
var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
      }
      .onDelete(perform: deleteListItem)
    }
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.printChecklistContents()
    }
  }
}

Moving list items

Just as List views have onDelete(perform:) to respond to the user’s gesture to delete a list item, they also have .onMove(perform:) to respond to the user’s gesture to move a list item.

var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
      }
      .onDelete(perform: deleteListItem)
    }
    .navigationBarItems(trailing: EditButton())
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.printChecklistContents()
    }
  }
}
The app with an ’Edit’ button in the navigation bar
Qlo ecy nigh um ’Ucem’ boblit ux mve muposiviut fuh

The app in edit mode, showing ’Delete’ buttons
Zfu anp ob ahev sepa, bbuzulk ’Xezogi’ cencadh

func moveListItem(whichElement: IndexSet, destination: Int) {
  checklistItems.move(fromOffsets: whichElement, toOffset: destination)
  printChecklistContents()
}
var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
      }
      .onDelete(perform: deleteListItem)
      .onMove(perform: moveListItem)	  
    }
    .navigationBarItems(trailing: EditButton())
    .navigationBarTitle("Checklist")
    .onAppear() {
      self.printChecklistContents()
    }
  }
}
The app in edit mode, showing ’Delete’ buttons and move handles
Pvu uxc ep olav daga, zbopeyh ’Seyeru’ kacqibw enq giru boqdtac

Dragging ’Eat ice cream’ to the top of the list
Sgoqgefm ’Oed olo glieq’ ya kye can ap kco beyz

Key points

In this chapter, you did the following:

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.