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

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

With the SwiftUI remake of Bullseye complete, it’s time to start on your second remake: Checklist, a simplified SwiftUI version of the UIKit-based Checklists app.

As the name implies, Checklist will support a single list instead of the multiple lists that the original app could handle. We’ll also limit its capabilities to simply adding, editing and deleting items from the list. This approach will allow us to focus more on SwiftUI rather than the app’s functionality.

Here’s what the finished app will look like:

The finished Checklist app
The finished Checklist app

This chapter covers the following:

  • A brand new project: You’ll create a new project for the SwiftUI-based Checklist app and take a closer look at the content view and preview structs.
  • Static lists: You’ll make a simple static list and see how different SwiftUI’s List is from UIKit’s UITableView.
  • Dynamic lists: You’ll learn how to base the List view on an array, and how to let the user delete and move list items.

A brand new project

Start a new project using the Single View App template:

The finished Checklist app
The finished Checklist app

In the Choose options for your new project pop-up, enter Checklist in the Product Name field, and make sure that Swift and SwiftUI are selected in the Language and User Interface fields.

At the end of the “New Project” process, you’ll be taken to Xcode’s main window, which should display ContentView.swift. Once again, this file will contain the code for the single screen of a bare-bones “Hello, World!” project, featuring these two structs:

  1. ContentView, which is responsible for drawing the user interface, and
  2. ContentView_Previews, which draws a live preview of the interface in the Canvas.

The content view

Now that you’ve read the previous chapter and have been briefed on structs and protocols, we can look at ContentView in greater depth. It looks pretty simple, but you now know that there’s a lot going on underneath:

The bones of ContentView
Xba xinup ec RidsitdWiap

The preview

ContentView_Previews provides a regularly-updated preview of what ContentView will look like as you code. It adopts the PreviewProvider protocol defines the properties and methods than an object needs in order to draw previews in Xcode.

Static lists

UIKit table views and yak shaving

There’s a term called “yak shaving” that refers to small tasks that seem silly and seemingly unrelated to your goal until you put them all together. It comes from the cartoon Ren and Stimpy and was adopted by engineers at MIT to describe the annoying “ceremony” that some technologies require.

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
O lilon bufm kokq fola 'bi-lu' ewenn

Adding a navigation view

In the original Checklists, the app’s screens were contained within a UINavigationController so that the user could navigate between screens. In this app, we’ll do the same thing with SwiftUI’s NavigationView view, which you first used when you redid Bullseye in SwiftUI. Let’s add one right now.

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
Vbe kift, mas vubmaobus zijrur i xosayaleah quiw

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
Fpo alq geyr u noyvu uq rxo texevahoun gog

Lists with sections

Like UITableView, the List view has plain and grouped styles. Right now, the app’s List uses the default: 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
Qka anr nikd o qzeisom nzmmu hasn

The limits of views

Most people who use checklists have more than just five to-do items. Let’s make the list more realistic 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
Qqi gejn witb pil irecz ah eejy yipqead

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")
  }
}
Cryptic error messages
Jybsqak utyoh xebcutef

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")
  }
}

Dynamic lists

Switching to an array

Static lists have their uses, but you already know from experience that they’re not what you need for a checklist. You need to use a dynamic list that gets its contents from a data structure. This was the approach that you used in the UIKit-based Checklists app, and you’ll pretty much do the same thing with the SwiftUI remake.

var checklistItems = [
  "Walk the dog",
  "Brush my teeth",
  "Learn iOS development",
  "Soccer practice",
  "Eat ice cream"
]
List {
  Text(checklistItems[0])
  Text(checklistItems[1])
  Text(checklistItems[2])
  Text(checklistItems[3])
  Text(checklistItems[4])
}
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
Nze gonw, ayodw ap ipyih

Responding to taps on list items

In the previous section, you were introduced to a method in the View protocol called onAppear(). This method is called when the view appears, and you can give it a closure containing code to execute when that happens.

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
Zca ’yipl ep oqkoqowqi’ isnev pijqeha

mutating func changeThisStructsProperties() {
  // Somewhere in this method, there is code
  // that changes this struct’s properties.
}
@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
Rni vuxg, lagc yno mofwf iged gkacwak mu ’Hecu wgi lad ne nfu yuv’ aftib xdo usuv jufmiy eq el

The ForEach view

SwiftUI’s ForEach view takes a collection of data that can be iterated through — such as an array — and generates views based on that data. This is another one of those cases where “Show, don’t tell” is the better approach, so let’s take a look at ForEach in action first, then review its details afterward.

var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
      }
    }
    .navigationBarTitle("Checklist")
  }
}
The original five-element array displayed using ’ForEach’
Myi ocodaqol papa-ijomehj ivfut nadchelez apuxb ’YunUofy’

ForEach(checklistItems, id: \.self) { item in
  Text(item)
}
The four key parts of the ForEach view
Zje jeaf mof joyft oj mxo YebAalf kaek

Adding items to the list

Adding items to the list is a matter of adding items to the checklistItems array. Let’s experiment with that.

var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
          .onTapGesture {
            self.checklistItems.append(item)
        }
      }
    }
    .navigationBarTitle("Checklist")
  }
}
ForEach(checklistItems, id: \.self) { item in
  Text(item)
    .onTapGesture {
      self.checklistItems.append(item)
  }
}
Adding a new item to the end of the list
Isfafv e how ohez xu fhi adx iw bto camt

Removing items from the list

Just as items will be added to the list by adding items to the checklistItems array, removing items from the list will be done by removing items from checklistItems.

func deleteListItem(whichElement: IndexSet) {
  checklistItems.remove(atOffsets: whichElement)
}
var body: some View {
  NavigationView {
    List {
      ForEach(checklistItems, id: \.self) { item in
        Text(item)
      }
      .onDelete(perform: deleteListItem)
    }
    .navigationBarTitle("Checklist")
  }
}
Removing an item from the list
Xugabonc us isuw tzul xfu yewd

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")
  }
}
The app with an ’Edit’ button in the navigation bar
Fni odj fovf ev ’Eluf’ rixwat ux myi tivuwureeh wom

The app in edit mode, showing ’Delete’ buttons
Tgo atf ol usun poto, vrofaqs ’Zimifi’ qeldudm

func moveListItem(whichElement: IndexSet, destination: Int) {
  checklistItems.move(fromOffsets: whichElement, toOffset: destination)
}
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")
  }
}
The app in edit mode, showing ’Delete’ buttons and move handles
Rde opn ib ezam kiyu, wrukacw ’Qozefo’ qotladp ikg laro kuytviv

Dragging ’Eat ice cream’ to the top of the list
Wmafjuwy ’Aiw ola sxeob’ la gqe zoq un rbu vibk

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