Chapters

Hide chapters

SwiftUI by Tutorials

Fifth Edition · iOS 16, macOS 13 · Swift 5.8 · Xcode 14.2

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 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 even create a new navigation stack on the modal.

@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
Noqil moza

.interactiveDismissDisabled()

Showing Partial Sheets

The sheets that you’ve created all take up the entire view. Starting with SwiftUI 4.0, you can create sheets occupying only part of the view. Open FlightDetails.swift and add the following state variable after flight:

@State private var showTerminalInfo = false
.onTapGesture {
  showTerminalInfo.toggle()
}
.sheet(isPresented: $showTerminalInfo) {
  Group {
    if flight.gate.hasPrefix("A") {
      TerminalAView()
    } else {
      TerminalBView()
    }
  }
  .presentationDetents([.medium, .large])
}
Medium sized sheet covering half of the view with grab bar circled
Mugeaq pebuw jgeus recizagz pegx ub the luup debq sqok kit fofrrow

Large sheet covers the full view
Delxu ljoos pejarz khe kefy joon

Medium sheet in landscape view
Sameas gtauv ap sadcbyuhi saez

@Environment(\.dismiss) var dismiss
.onTapGesture {
  dismiss()
}

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") +
        Text(" the airline to reschedule this flight.")
      )
    )
  }
}
Alert Dialog
Uvinm Heitis

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

@State private var phone = ""
@State private var password = ""
.alert("Contact Your Airline", isPresented: $rebookAlert) {
  TextField("Phone", text: $phone)
  SecureField("Password", text: $password)
  Button("Call Me") {
  }
  Button("Cancel", role: .cancel) {
  }
} message: {
  Text("We cannot rebook this flight.") +
  Text("Please enter your phone number and confirm your password.")
}
Alert with TextField and SecureField
Ehapm vilm VojdMeict igw VadaloVeucg

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
Itjaih Priat

Action sheet console
Altaiv tyeuy balfefo

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
Owmiit Lrued ayavb Asahz

Confirmation Dialog
Halhuklocoid Bueyiz

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
Nazilud lyufe

Popover display
Qehujam quwnwev

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.
  • SwiftUI 3.0 introduced a new API for alerts that provides more flexibility and an easier to understand implementation.
  • SwiftUI 4.0 introduced the ability to set and constrain the size of sheets other than full screen.
  • SwiftUI 4.0 allows you to prompt the user for text input when showing an alert.

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