Home iOS & Swift Books SwiftUI by Tutorials

14
Lists Written by Bill Morefield

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.

Most apps focus on displaying some type of data to the user. Whether upcoming appointments, past orders or new products, you must clearly show the user the information they come to your app for.

In the previous chapter, you saw a preview of iterating through data when displaying the flights for a day and allowing the user to interact with this data. In this chapter, you’ll dive deeper into the ways SwiftUI provides you to show a list of items to the user of your app.

Iterating through data

Open the starter project for this chapter and go to FlightList.swift in the FlightStatusBoard group. You’ll see a slightly different view than the one you created in the previous chapter. In place of List, which you’ll work with later in this chapter, you’ll start by examining ForEach.

SwiftUI uses ForEach as a fundamental element to loop over data. When you pass it a collection of data, it then creates multiple sub-views using a provided closure, one for each data item. ForEach works with any type of collected data. You can think of ForEach as the SwiftUI version of the for-in loop in traditional Swift code.

Run the app, tap Flight Status — and you’ll notice a mess.

Bad schedule
Bad schedule

Remember that ForEach operates as an iterator. It doesn’t provide any structure. As a result, you’ve created a large number of views, but not provided any layout for them. They’re all at the top level, not contained in anything else. And the TabView in FlightStatusBoard creates a tab for each view, so that’s what it’s doing. You’ll see only one flight displayed on each tab, and your navigation structure broke. To fix both issues, add some structure to the view:

ScrollView {
  VStack {
    ForEach(flights, id:\.id) { flight in
      NavigationLink(
        destination: FlightDetails(flight: flight)) {
        FlightRow(flight: flight)
      }
    }.navigationBarTitle("Flight Status")
  }
}

You wrapped the ForEach loop inside a VStack — giving you a vertical stack of rows — and a ScrollView — that allows scrolling the rows since there’s more content than will fit onto the view. SwiftUI picks up that you’ve wrapped a VStack and applies vertical scrolling to match. If a line of text within the view became longer than the view’s width, SwiftUI wouldn’t automatically add horizontal scrolling.

Scrolled list
Scrolled list

You can override this default scrolling direction by passing in the desired scroll axes to ScrollView. To scroll the view in both directions, you would change the call to:

ScrollView([.horizontal, .vertical]) {

ScrollView provides a useful, general way to let a user browse through data that won’t fit onto a single screen.

Also note the id: parameter passed a keypath to a property of the type in the array. This parameter hints that SwiftUI has expectations for the data sent to an iteration. In the next section, you’ll explore these expectations and make your data work more smoothly with SwiftUI.

Making your data work better with iteration

The data passed into ForEach must provide a way to identify each element of the array as unique. In this loop, you use the id: parameter to tell SwiftUI to use the \.id property of FlightInformation as the unique identifier for each element in the array.

extension FlightInformation: Identifiable {
}
ForEach(flights) { flight in
Foreach identifiable
Mamiagv aqujcapeifvi

Improving performance

When a VStack or HStack renders, SwiftUI creates all the cells at once. For a view such as this with only thirty rows, that probably doesn’t matter. For rows with hundreds of potential rows, that’s a waste of resources since most are not visible to the user. Using the Lazy versions of these stacks introduced in SwiftUI 2.0 (iOS 14, macOS 11, etc.) provides a quick performance improvement when iterating over large data sets.

Lazy vs not
Quwm qv zug

Setting the scroll position in code

A major weakness of the first version of SwiftUI was the lack of a way to set the scrolling position programmatically. The second version introduced with iOS 14 and macOS Big Sur added ScrollViewReader that allows setting the current position from code. You’ll use it to scroll the flight status list to the next flight automatically. Change the view to:

ScrollViewReader { scrollProxy in
  ScrollView {
    LazyVStack {
      ForEach(flights) { flight in
        NavigationLink(
          destination: FlightDetails(flight: flight)) {
          FlightRow(flight: flight)
        }
      }
    }
  } // onAppear
}
var nextFlightId: Int {
  guard let flight = flights.first(
    where: {
      $0.localTime >= Date()
    }
  ) else {
    return flights.last!.id
  }
  return flight.id
}
.onAppear {
  // 1
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
    // 2
    scrollProxy.scrollTo(nextFlightId)
  }
}
Scroll
Fxdejq

scrollProxy.scrollTo(nextFlightId, anchor: .center)
Late view
Gicu teaj

Creating lists

SwiftUI provides the List struct that does the heavy lifting for you and uses the platform-specific control to display the data. A List is a container much like a VStack or HStack that you can populate with static views, dynamic data or other iterative views.

ScrollViewReader { scrollProxy in
  List(flights) { flight in
    NavigationLink(
      destination: FlightDetails(flight: flight)) {
      FlightRow(flight: flight)
    }
  }.onAppear {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
      scrollProxy.scrollTo(nextFlightId, anchor: .center)
    }
  }
}
Flight list
Czargt wols

Building search results

To start building the search view, open SearchFlights.swift under the SearchFlights group. You’ll see view to allow the user to search for flights. However, the search functionality isn’t in place yet. In this section, you’re going to fix that.

@State private var city = ""
.searchable(text: $city)
New Search Field
Qep Faoxcr Suuxz

if !city.isEmpty {
  matchingFlights = matchingFlights.filter {
    $0.otherAirport.lowercased().contains(city.lowercased())
  }
}
List(matchingFlights) { flight in
  SearchResultRow(flight: flight)
}
Searching flights
Roejxbivn fwavlzh

Building a hierarchical list

The second version of SwiftUI added support for displaying hierarchical data. Much as the NavigationLink gave you a structure to organize views from general to more specific, a hierarchical list gives you an excellent way to display data that moves from general to more specific. In this section, you will update the search results into a hierarchical list that displays dates and then displays the flights for that date under it.

Hierarchical list
Loegimjcumaj loxx

struct HierarchicalFlightRow: Identifiable {
  var label: String
  var flight: FlightInformation?
  var children: [HierarchicalFlightRow]?

  var id = UUID()
}
func hierarchicalFlightRowFromFlight(_ flight: FlightInformation)
  -> HierarchicalFlightRow {
  return HierarchicalFlightRow(
    label: longDateFormatter.string(from: flight.localTime),
    flight: flight,
    children: nil
  )
}
var flightDates: [Date] {
  let allDates = matchingFlights.map { $0.localTime.dateOnly }
  let uniqueDates = Array(Set(allDates))
  return uniqueDates.sorted()
}
func flightsForDay(date: Date) -> [FlightInformation] {
  matchingFlights.filter {
    Calendar.current.isDate($0.localTime, inSameDayAs: date)
  }
}
var hierarchicalFlights: [HierarchicalFlightRow] {
  // 1
  var rows: [HierarchicalFlightRow] = []

  // 2
  for date in flightDates {
    // 3
    let newRow = HierarchicalFlightRow(
      label: longDateFormatter.string(from: date),
      // 4
      children: flightsForDay(date: date).map {
        hierarchicalFlightRowFromFlight($0)
      }
    )
    rows.append(newRow)
  }
  return rows
}
// 1
List(hierarchicalFlights, children: \.children) { row in
  // 2
  if let flight = row.flight {
    SearchResultRow(flight: flight)
  } else {
    Text(row.label)
  }
}
Hierarchical flight list
Fiofiydweyan qsuwxk wosn

Grouping list items

A long list of data can be challenging for the user to read. Fortunately, the List view supports breaking a list into sections. Combining dynamic data and sections moves into some more complex aspects of displaying data in SwiftUI. In this section, you’ll separate flights into sections by date and add a header and footer to each section.

// 1
List {
  // 2
  ForEach(flightDates, id: \.hashValue) { date in
    // 3
    Section(
      // 4
      header: Text(longDateFormatter.string(from: date)),
      // 5
      footer:
        HStack {
          Spacer()
          Text("Matching flights " +
                "\(flightsForDay(date: date).count)")
        }
    ) {
      // 6
      ForEach(flightsForDay(date: date)) { flight in
        SearchResultRow(flight: flight)
      }
    }
  }
  // 7
}.listStyle(InsetGroupedListStyle())
Grouped
Kloidaj

Key points

  • A ScrollView wraps a view within a scrollable region that doesn’t affect the rest of the view.
  • The ScrollViewProxy lets you change the current position of a list from code.
  • SwiftUI provides two ways to iterate over data. The ForEach option loops through the data allowing you to render a view for each element.
  • A List uses the platform’s list control to display the elements in the data.
  • Data used with ForEach and List must provide a way to identify each element uniquely. You can do this by specifying an attribute that implements the Hashable protocol, have the object implement Hasbable and pass it to the id parameter or have your data implement the Identifiable protocol.
  • Building a hierarchical view requires a hierarchical data structure to describe how the view should appear.
  • You can split a List in Sections to organize the data and help the user understand what they see.
  • You can combine ForEach and List to create more complex data layouts. This method works well when you want to group data into sections.

Where to go from here?

For more on integrating navigation and views, look at SwiftUI Tutorial: Navigation at https://www.raywenderlich.com/5824937-swiftui-tutorial-navigation.

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.