Chapters

Hide chapters

SwiftUI by Tutorials

Second Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Building Blocks of SwiftUI

Section 2: 6 chapters
Show chapters Hide chapters

11. Lists & Navigation
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.

It’s a rare app that can work with only a single view; most apps use many views and must provide a way for the user to navigate between them smoothly. The navigation you design has to balance many needs: you need to display data logically to the user, you need to provide an easy way to move between views, and you need to make it easy for the user to figure out how to perform a particular task.

SwiftUI provides a unified interface to manage navigation while also displaying data. In this chapter, you’ll explore how to display data to the user, while also building several types of navigation between views.

Getting started

Open the starter project for this chapter; you’ll find a very early version of an app for an airport. In this chapter, you will build out the app to display today’s flight arrivals and departures. In a real-world app, you would likely get this information from an API through Combine. For this app, though, you’ll be using mock data.

Open the Models folder in the app, and you’ll see two files. The first is FlightInformation.swift, which encapsulates information about flights. It contains a static method generateFlight() that generates test data for one flight, and another static method generateFlights() that generates an array of thirty flights. The other is ContentView.swift, which contains a variable flightInfo in which the app stores a new set of flights each time the app runs.

Navigating through a SwiftUI app

When designing the navigation for your SwiftUI app, you must create a navigation pattern that helps the user to move confidently through the app and intuitively perform tasks. Your users will hardly ever notice navigation that’s done well, but they won’t stand for an app that’s hard to navigate or one that makes it hard to find information. SwiftUI is a cross-platform framework but takes its primary design inspiration from iOS and iPadOS. Therefore, SwiftUI integrates patterns and design guidelines that are common on those platforms.

Flat navigation
Npeq musaboheok

Hierarchical navigation
Jaaliqbxudup neletobuoq

Creating navigation views

You’ll first set up a tab view in the sample app. Open ContentView.swift and change the body of the view as follows:

// 1
TabView {
  // 2
  FlightBoard()
    // 3
    .tabItem({
      // 4
      Image(systemName: "icloud.and.arrow.down")
        .resizable()
      Text("Arrivals")
    })
  FlightBoard()
    .tabItem({
      Image(systemName: "icloud.and.arrow.up")
        .resizable()
      Text("Departures")
    })
}
var boardName: String
FlightBoard(boardName: "Test")
Text(boardName)
  .font(.title)
FlightBoard(boardName: "Arrivals")
FlightBoard(boardName: "Departures")

Using navigation views

A navigation view arranges multiple views into a stack, transitioning from one view to another. In each view, the user makes a single choice that continues to a new view in the stack. You can go backward in the stack, but you can’t jump between different children in the stack. On a large-screen device, SwiftUI also supports a split-view interface, which separates the main views of the app into separate panes. One view generally remains static, while the second changes as the user navigates through the view stack.

// 1
NavigationView {
  ZStack {
    Image(systemName: "airplane")
      .resizable()
      .aspectRatio(contentMode: .fit)
      .opacity(0.1)
      .rotationEffect(.degrees(-90))
      .frame(width: 250, height: 250, alignment: .center)
    VStack(alignment: .leading, spacing: 5) {
      // 2
      NavigationLink(destination: FlightBoard(
        boardName: "Arrivals")) {
          // 3
          Text("Arrivals")
        }
      NavigationLink(destination: FlightBoard(
        boardName: "Departures")) {
          Text("Departures")
        }
      Spacer()
    }
    .font(.title)
    .padding(20)
  // 4
  }
  .navigationBarTitle(Text("Mountain Airport"))
}

Displaying a list of data

Open the file FlightBoard.swift. Right now, this is a default SwiftUI view. You will update it to display the flight information for arriving or departing flights, depending on what’s passed in as arguments.

var flightData: [FlightInformation]
FlightBoard(boardName: "Test", 
            flightData: FlightInformation.generateFlights())
NavigationLink(destination: FlightBoard(
  boardName: "Arrivals",
  flightData: self.flightInfo.arrivals())) {
    Text("Arrivals")
  }
NavigationLink(destination: FlightBoard(
  boardName: "Departures",
  flightData: self.flightInfo.departures())) {
    Text("Departures")
  }
VStack {
  Text(boardName)
    .font(.title)
  ForEach(flightData, id: \.id) { flight in
    Text("\(flight.airline) \(flight.number)")
  }
}

Making your data more compatible 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 as the unique identifier for each element in the array. The only requirement for the unique identifier is to implement the Hashable protocol, which the native Swift String and Int types do already. You can also use the Foundation UUID and URL types if need be. As .id is an Int, it works just fine as the unique identifier.

extension FlightInformation: Identifiable {

}
ForEach(flightData) { flight in

Showing scrolling data

Open FlightBoard.swift and change the body of the view to:

VStack {
  Text(boardName)
    .font(.title)
  ForEach(flightData) { flight in
    VStack {
      Text("\(flight.airline) \(flight.number)")
      Text("\(flight.flightStatus) at \(flight.currentTimeString)")
      Text("At gate \(flight.gate)")
    }
  }
}

ScrollView {
  ForEach(flightData) { flight in
    VStack {
      Text("\(flight.airline) \(flight.number)")
      Text("\(flight.flightStatus) at \(flight.currentTimeString)")
      Text("At gate \(flight.gate)")
    }
  }
}

ScrollView([.horizontal, .vertical]) {

Creating lists

ForEach iterates over the elements of the array, but it relies on you to figure out what to do with that data. Since iterating through data and displaying it to the user is such a common task, all platforms have a built-in control for this task. SwiftUI provides the List struct, in addition to ForEach, that does the heavy lifting for you.

VStack {
  Text(boardName)
    .font(.title)
  List(flightData) { flight in
    Text("\(flight.airline) \(flight.number)")
  }
}

NavigationView {
  List(flightData) { flight in
    Text("\(flight.airline) \(flight.number)")
  }
}

.navigationBarTitle(boardName)

var flight: FlightInformation
HStack {
  Text("\(self.flight.airline) \(self.flight.number)")
    .frame(width: 120, alignment: .leading)
  Text(self.flight.otherAirport)
    .frame(alignment: .leading)
  Spacer()
  Text(self.flight.flightStatus)
    .frame(alignment: .trailing)
}
FlightRow(flight: FlightInformation.generateFlight(0))
FlightRow(flight: flight)

Adding navigation links

Create a new SwiftUI view named FlightBoardInformation.swift. You’ll use this view to provide more detailed information about the flight to the user.

var flight: FlightInformation
VStack(alignment: .leading) {
  HStack{
    Text("\(flight.airline) Flight \(flight.number)")
      .font(.largeTitle)
    Spacer()
  }
  Text("\(flight.direction == .arrival ? "From: " : "To: ")" +
    "\(flight.otherAirport)")
  Text(flight.flightStatus)
    .foregroundColor(Color(flight.timelineColor))
  Spacer()
}
.font(.headline)
.padding(10)
FlightBoardInformation(flight:
  FlightInformation.generateFlight(0))
List(flightData) { flight in
  NavigationLink(destination: FlightBoardInformation(flight: flight)) {
    FlightRow(flight: flight)
  }
}
.navigationBarTitle(boardName)

Adding items to the navigation bar

Each view in the navigation view stack has a navigation bar. By default, the navigation bar contains a link back to the previous view. You can add additional items to the navigation bar if you need to, although you want to avoid overcrowding it with too many controls.

@State private var hideCancelled = false
var shownFlights: [FlightInformation] {
  hideCancelled ?
    flightData.filter { $0.status != .cancelled } :
    flightData
}
List(shownFlights) { flight in
.navigationBarItems(trailing:
  Toggle(isOn: $hideCancelled, label: {
    Text("Hide Cancelled")
  })
)

Key points

  • App navigation generally combines a mix of flat and hierarchical flows between views.
  • Tab views display a flat navigation that allows quick switching between the views.
  • Navigation views create a hierarchy of views as a view stack. The user can move further into the stack and can back up from within the stack.
  • A navigation link connects a view to the next view in the view stack.
  • You should only have one NavigationView in a view stack. Views that follow should inherit the existing navigation view.
  • You apply changes to the navigation view stack to controls in the stack, and not to the NavigationView itself.
  • A ScrollView wraps a section of a view within a scrollable region that doesn’t affect the rest of the view.
  • 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 uniquely identify each element. You can do this by specifying an attribute that implements the Hashable protocol, have the object implement Hasbable or have your data implement the Identifiable protocol.

Where to go from here?

The first stop when looking for information on user interfaces on Apple platforms should be the Human Interface Guidelines on Navigation for iOS, watchOS and tvOS:

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