Chapters

Hide chapters

SwiftUI by Tutorials

Fourth Edition · iOS 15, macOS 12 · Swift 5.5 · Xcode 13.1

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

17. Sheets & Alert Views
Written by Bill Morefield

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In a previous chapter, you learned how to use standard navigation to switch between views in your app. However, sometimes you need to display a view to the user only under certain conditions. You’ll often use these views when showing important messages that interrupt the user’s current context and need direct feedback or response before continuing.

Presenting a view outside the navigation stack lets the user’s focus remain on the task they initiated. It also provides a way for your app to provide critical information or request essential feedback.

Starting in SwiftUI 3.0, Apple appears to be shifting the approach to these views. The initial versions of SwiftUI focused on the type of view to display. The changes to APIs and new modifiers in SwiftUI 3.0 indicate a shift to the view’s purpose instead of the kind of view. In this chapter, you’ll expand the app to use different conditional views in SwiftUI. Along the way, you’ll explore the new SwiftUI 3.0 APIs to prepare your app for the future.

Displaying a modal sheet

In Chapter 14: Lists, you built a view allowing users to search for a flight. One element deferred then was the ability to view details or interact with those results. You’re going to add that ability in this chapter. Modal sheets help focus the user’s attention on the current view without building through the overall navigation hierarchy. The modal sheet slides a new view over the current view.

SwiftUI provides two ways to display a modal, both based on a @State variable in the view. The first method uses a Bool variable that you set to true when the sheet should display. The second method uses an optional state variable that shows the modal when the variable becomes non-nil. You’ll use the Bool method for this modal.

All modals provide these two options; you’ll see an example using an optional variable later in this chapter.

Open the starter project for this chapter; you’ll find the project from the end of the last chapter. Go to SearchResultRow.swift. Notice the view for each row now resides in a separate view. That will make the code changes for this chapter a little cleaner. Add the following new variable after flight:

@State private var isPresented = false

This line defines a @State variable that indicates when to show the modal sheet. Change the view to:

// 1
Button(
  action: {
    isPresented.toggle()
  }, label: {
    FlightSearchSummary(flight: flight)
  })
  // 2
  .sheet(
    // 3
    isPresented: $isPresented,
    // 4
    onDismiss: {
      print("Modal dismissed. State now: \(isPresented)")
    },
    // 5
    content: {
      FlightSearchDetails(flight: flight)
    }
  )

Here’s what the elements of the modal sheet do:

  1. You wrap the row inside a button. The action of the button toggles the state variable.
  2. To tell SwiftUI you want to display a modal, you call sheet(isPresented:onDismiss:content:). This call must attach to an element of the view.
  3. Here, you pass the isPresented state variable you added earlier, which tells SwiftUI to show the modal when the variable becomes true. When the user dismisses the modal, SwiftUI sets the state back to false.
  4. The optional onDismiss: is a closure you can use to execute code after the user dismisses the modal. In an app, this would be the place to react to user actions in the modal. You print a message to the console and show that the state variable’s value is now false.
  5. You provide the view to show on the modal sheet as the closure for sheet(isPresented:onDismiss:content:). For the moment, you’ll use the existing FlightSearchDetails(flight:) view.

Build and run, navigate to Search Flights and tap any row to see the modal appear. Swipe down on the modal to dismiss it. In the debug console, you’ll see the state variable become false after you dismiss the modal:

Initial Modal view
Initial Modal view

Programmatically dismissing a modal

You probably noticed that the navigation view disappears in the modal sheet. That’s because a modal sheet takes over the whole screen and no longer wraps the view in any existing navigation view. You can create a new navigation view on the modal, making an entirely new navigation view stack.

@Binding var showModal: Bool
HStack {
  FlightDetailHeader(flight: flight)
  Spacer()
  Button("Close") {
    showModal = false
  }
}
FlightSearchDetails(
  flight: FlightData.generateTestFlight(date: Date()),
  showModal: .constant(true)
).environmentObject(AppEnvironment())
FlightSearchDetails(
  flight: flight,
  showModal: $isPresented
)
Modal done
Ripug doci

.interactiveDismissDisabled()

Creating an alert

Alerts bring something important to the user’s attention, such as a warning about a problem or a request to confirm an action that could have severe consequences.

@State private var rebookAlert = false
// 1
if flight.status == .canceled {
  // 2
  Button("Rebook Flight") {
    rebookAlert = true
  }
  // 3
  .alert(isPresented: $rebookAlert) {
    // 4
    Alert(
      title: Text("Contact Your Airline"),
      message: Text(
        "We cannot rebook this flight. Please contact the airline to reschedule this flight."
      )
    )
  }
}
Alert Dialog
Emamy Poehey

// 1
.alert("Contact Your Airline", isPresented: $rebookAlert) {
  // 2
  Button("OK", role: .cancel) {
  }
  // 3
} message: {
  Text(
    "We cannot rebook this flight. Please contact the airline to reschedule this flight."
  )
}
Alert Dialog
Awoyh Zeakam

Adding an action sheet

An action sheet should appear in response to a user action, and the user should expect it to appear. For example, you might want to use an action sheet to confirm an action or let the user select between multiple options.

import SwiftUI

struct CheckInInfo: Identifiable {
  let id = UUID()
  let airline: String
  let flight: String
}
@State private var checkInFlight: CheckInInfo?
// 1
if flight.isCheckInAvailable {
  Button("Check In for Flight") {
    // 2
    checkInFlight =
      CheckInInfo(
        airline: flight.airline,
        flight: flight.number
      )
  }
  // 3
  .actionSheet(item: $checkInFlight) { checkIn in
    // 4
    ActionSheet(
      title: Text("Check In"),
      message: Text("Check in for \(checkIn.airline)" +
        "Flight \(checkIn.flight)"),
      // 5
      buttons: [
        // 6
        .cancel(Text("Not Now")),
        // 7
        .destructive(Text("Reschedule")) {
          print("Reschedule flight.")
        },
        // 8
        .default(Text("Check In")) {
          print(
            "Check-in for \(checkIn.airline) \(checkIn.flight)."
          )
        }
      ]
    )
  }
}
Action Sheet
Ungoeg Gpean

Action sheet console
Ijciof vqiox bifdame

Using alerts as action sheets

The new SwiftUI alert format allows it to work very similarly to an action sheet. In this section, you’ll implement the action sheet from the last section using the new alert.

@State private var showCheckIn = false
Button("Check In for Flight") {
  checkInFlight =
    CheckInInfo(
      airline: flight.airline,
      flight: flight.number
    )
  showCheckIn = true
}
// 1
.alert("Check In", isPresented: $showCheckIn, presenting: checkInFlight) { checkIn in
  // 2
  Button("Check In") {
    print(
      "Check-in for \(checkIn.airline) \(checkIn.flight)."
    )
  }
  // 3
  Button("Reschedule", role: .destructive) {
    print("Reschedule flight.")
  }
  // 4
  Button("Not Now", role: .cancel) { }
  // 5
} message: { checkIn in
  Text("Check in for \(checkIn.airline)" +
    "Flight \(checkIn.flight)")
}
Action Sheet using Alert
Isfiem Kyiaq asakr Unocj

Confirmation Dialog
Cewdokyiraoh Ziehuc

Showing a popover

Like the action sheet, you usually display a popover in response to a user action. Popovers work best on larger-screen devices, such as iPads and Macs. On devices with smaller screens, a full-screen view, such as a modal sheet, better serves your needs. If the screen is too tiny, SwiftUI renders the popover as a modal sheet instead.

@State private var showFlightHistory = false
Button("On-Time History") {
  showFlightHistory.toggle()
}
.popover(
  isPresented: $showFlightHistory,
  arrowEdge: .top) {
  FlightTimeHistory(flight: flight)
}
Popover phone
Jusoniy glegu

Popover display
Sabiyuy celnziz

Key points

  • Modal sheets display on top of the view. You can use either a Bool state variable or an optional state variable that implements the Identifiable protocol to tell SwiftUI to display them.
  • The alert, action sheet and popover views provide a standard way to display information to the user and collect feedback.
  • SwiftUI 3.0 introduced a new API for alerts that provides more flexibility and an easier to understand implementation.
  • Alerts generally display information about unexpected situations or confirm actions that have severe consequences.
  • Action sheets and popovers display in response to a user action. You use action sheets for smaller screen devices and popovers on larger screens.

Where to go from here?

As mentioned in a previous chapter, the first stop for information on user interfaces on Apple platforms should be the Human Interface Guidelines on Modality for the appropriate SwiftUI operating systems:

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 accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now