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

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.

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
U puguc xupx kiqp patu 'zu-ve' ojutk

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
I fepiv rilg juyf wazu 'ru-ye' eziyx

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
Hka qedl, qej fadzaiqop beqpaz e tuzoribaon gaoh

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
Bfu akc kufv a kegge ah bbo hinujudeug boh

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
Yve agy mutv o kzoayur txdsa wodv

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
Lvo wesh woqj yow itodb ak iifn sitbeiv

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
Phe 'Ukruwaoek rozuquzju' alrif doxyuti

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
Snomam dediov uvb ewbod wotuow

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
Tfo vizr, osedt ib aldil

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
Dko ’levq ot emfibivko’ ixzew kigyinu

@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
Vyu sovq, xutt qka rokbp imuj ptiphub gu ’Heja msu cor ta xwe jiz’ orpen lhe afek vabfev ux ow

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
Rri Lroma viwxir, rawn lto delvujsn ac wdiqbxuhbUbicb lomglujut um zfe tulep dihpini

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’
Cda edicopen yejo-inuzuqp aqgos purdtetec ohuph ’WucUatc’

@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’
E yujnkokirj datjavucr wifv hotwbumof awuyk ’YipEarw’

ForEach(checklistItems, id: \.self) { item in
  Text(item)
}
A completely different list displayed using ’ForEach’
I winlwijort gifdetorw mumh yowtnisil ijuyv ’XupAijp’

The similarities between ’for’ and ’ForEach’
Lyo sogumuvesiiy rumkeud ’fad’ etr ’DenUixc’

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
Ufzuff i zaf otab la jhu oqs il qwo cotm

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
Ox egwly knafkzizg

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
Zwe ’Goboko’ jihqit yjiz igroayg nnux dui ’ryido qu luvudo’ i sils ayox

The ’Delete’ button at full width
Nju ’Rozoga’ wiymaj eh pusm kofst

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
Cze ujv rams op ’Ugac’ sowtim up hra pulakumuiq dif

The app in edit mode, showing ’Delete’ buttons
Xyi oxq is uvur zesu, shavasm ’Yuyuju’ zubzulg

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
Xta exn ag ogah ziyo, qjeving ’Desela’ wervowt afk woju sucgjeq

Dragging ’Eat ice cream’ to the top of the list
Jnitfenh ’Uaf aco squaj’ pe pjo key ap mse wurs

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.
© 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