Modal views in SwiftUI iOS 07.09.2024

A modal view is a view that shows up on top of the currently displayed view and that prevents interaction with the underlying view. The modal view is used to capture user input or display additional information without navigating away from the current screen. Once a modal view appears, it needs to be dismissed before interaction is possible with the rest of the application.

Showing a modal sheet (iOS 15+)

Sheets are used to display a view on top of another one, and they can be dismissed by dragging them down or programmatically. You need a Boolean that controls whether the sheet is presented.

struct SheetView: View {
    @Environment(\.dismiss) var dismiss
    var body: some View {
        Button("Dismiss") {
            dismiss()
        }
    }
}

struct ContentView: View {
    @State var isVisible = false
    var body: some View {
        Button("Show") {
            isVisible.toggle()
        }
        .sheet(isPresented: $isVisible) {
            SheetView()
        }
    }
}

Here the @State property is used to control the presentation of the sheet. It is initialized to false, meaning the sheet is not shown initially.

If you want your modal sheet to cover a view entirely and not be dismissible by dragging, you can use the .fullScreenCover modifier.

struct ContentView: View {
    @State var isVisible = false
    var body: some View {
        Button("Show") {
            isVisible.toggle()
        }
        .fullScreenCover(isPresented: $isVisible, content: SheetView.init)
    }
}

Showing alerts (iOS 15+)

To show an alert the approach is similar to that used for sheets: you need a Boolean to decide whether the alert is shown, and then you attach the alert() modifier to the main view and include all the buttons you need inside the alert.

struct ContentView: View {
    @State var isVisible = false
    var body: some View {
        Button("Show") {
            isVisible.toggle()
        }
        .alert("Here is the Alert", isPresented: $isVisible) {
            Button("OK", role: .cancel) { }
        }
    }
}

The popover (iOS 13.0+)

The popover was originally an iPad-only view presented modally and originates from the point where it is invoked, generally on a button.

The popover function in SwiftUI is a view modifier. It takes four main parameters:

  • isPresented. A binding variable to control the visibility of the popover.
  • attachmentAnchor. Specifies where the popover anchors. It defaults to the bounds of the target view.
  • arrowEdge. Indicates the edge where the popover’s arrow points. It defaults to the top edge.
  • content. A closure that generates the content inside the popover.

popover modifier returns a modified view that shows the popover whenever isPresented is set to true.

struct ContentView: View {
    @State var isVisible = false
    var body: some View {
      Button("Show") {
        isVisible.toggle()
      }
      .popover(isPresented: $isVisible) {
        Text("Here is the Popover")
        .padding()
      }
    }
}

For an iPhone, you will get the very same behavior and appearance as a modal sheet.

Presentation detents (iOS 16+)

The .presentationDetents modifier allows you to present a modal sheet which shows up only partially to cover the screen, in steps you can specify.

If you don’t specify any detent, the default is going to be .large. You can specify .medium (half screen) or .large (full length) – use an array of floating point values, a floating point fraction of the available height (.fraction()), or give a precise height in points with .height(). If you are developing a horizontal app, you should provide a button to dismiss the sheet. If you want to display a drag indicator, use the .presentationDragIndicator(.visible) modifier to show it.

struct ContentView: View {
    @State var isVisible = false
    var body: some View {
        Button("Show") {
            isVisible.toggle()
        }
        .sheet(isPresented: $isVisible) {
            SheetView()
              .presentationDetents([.height(250)])
              .presentationDragIndicator(.visible)            
        }
    }
}