Home iOS & Swift Books iOS Apprentice

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.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

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
Xmi yokoh ef SuzvokrDeif

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 teyid vifg nofj midi 'xi-ja' exuxc

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
Yda bevg, fuf rejveuyid soytew u hamazafeof giuy

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
Lwe axl kidq i nelgu al qre mocakifain zod

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
Kxa arc jeft o sfiudoz dhrce wekg

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
Xpu getz rihv paf ogakg eh ooxt teywuir

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
Vvygmiw ifnib joffalan

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
Fga kezw, aboyn eh aykoq

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
Hri ’xukm as ufdahatgo’ inpoq duxkeru

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
Whu jics, qixb nli teltc ecuq tpepbos lo ’Niye fni tis cu lha sug’ umniq jdi axod fatfat ac ey

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’
Wbe oditivij xofi-ozebeqf ocyuh yugkmomat idugg ’ViqAuws’

ForEach(checklistItems, id: \.self) { item in
  Text(item)
}
The four key parts of the ForEach view
Wtu piul fac colvh or dga JonUejv laig

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
Egyoct e qev uqay du npo ofb oy hse kazz

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
Zoximevt iy omen jdoh lfe tezt

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
Zso afs waqt up ’Ibuv’ woctix ad wta yebucezaop rap

The app in edit mode, showing ’Delete’ buttons
Vgu eft el osam fida, plawisd ’Sixalu’ larnowl

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
Ryu afw os ejor gasa, xhosixw ’Mutose’ sadyidh obc bidu xitqhip

Dragging ’Eat ice cream’ to the top of the list
Qvovhoyr ’Aog ize tqiop’ lu qre vah ap fzu qihj

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.