Chapters

Hide chapters

SwiftUI by Tutorials

Third Edition · iOS 14 · Swift 5.3 · Xcode 12

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

16. 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 the 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.

Open the starter project for this chapter; you’ll find the project from the end of the last chapter.

In this chapter, you’ll expand the app to use different conditional views in SwiftUI.

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 are useful when you want to 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 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: \(self.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. Here, 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, but it makes an entirely new navigation view stack.

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

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
Ucelp Toabuw

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
    self.checkInFlight =
      CheckInInfo(
        airline: self.flight.airline,
        flight: self.flight.number
      )
  }
  // 3
  .actionSheet(item: $checkInFlight) { flight in
    // 4
    ActionSheet(
      title: Text("Check In"),
      message: Text("Check in for \(flight.airline)" +
        "Flight \(flight.flight)"),
      // 5
      buttons: [
        // 6
        .cancel(Text("Not Now")),
        // 7
        .destructive(Text("Reschedule"), action: {
          print("Reschedule flight.")
        }),
        // 8
        .default(Text("Check In"), action: {
          print(
            "Check-in for \(flight.airline) \(flight.flight)."
          )
        })
      ]
    )
  }
}
Action Sheet
Ewcoap Cwief

Action sheet console
Ugluen zfaeg yexfiva

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: self.flight)
}
Popover phone
Surequj cmesi

Popover display
Xuvotap ticfjug

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.
  • 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 the 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