SwiftUI
iOS Development
Swift Programming
Sheet Presentation
User Interface Design

Multiple sheetisPresented doesn't work in SwiftUI

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In SwiftUI, attaching multiple .sheet(isPresented:) modifiers to the same view often leads to only one sheet showing reliably. The framework applies presentation modifiers in a specific order, and competing booleans can conflict. A better pattern is one sheet modifier driven by a single enum state.

Why Multiple Boolean Sheets Conflict

When two booleans become true around the same time, SwiftUI has to pick one presentation path. This can cause ignored actions or the wrong modal appearing.

swift
1struct BadExampleView: View {
2    @State private var showSettings = false
3    @State private var showProfile = false
4
5    var body: some View {
6        VStack {
7            Button("Settings") { showSettings = true }
8            Button("Profile") { showProfile = true }
9        }
10        .sheet(isPresented: $showSettings) { Text("Settings") }
11        .sheet(isPresented: $showProfile) { Text("Profile") }
12    }
13}

This pattern is brittle as view complexity grows.

Use Enum Driven Sheet State

A single optional enum keeps intent explicit and scales well.

swift
1import SwiftUI
2
3enum ActiveSheet: Identifiable {
4    case settings
5    case profile
6    case help
7
8    var id: String {
9        switch self {
10        case .settings: return "settings"
11        case .profile: return "profile"
12        case .help: return "help"
13        }
14    }
15}
16
17struct ContentView: View {
18    @State private var activeSheet: ActiveSheet?
19
20    var body: some View {
21        VStack(spacing: 16) {
22            Button("Open Settings") { activeSheet = .settings }
23            Button("Open Profile") { activeSheet = .profile }
24            Button("Open Help") { activeSheet = .help }
25        }
26        .sheet(item: $activeSheet) { sheet in
27            switch sheet {
28            case .settings:
29                SettingsView()
30            case .profile:
31                ProfileView()
32            case .help:
33                HelpView()
34            }
35        }
36    }
37}
38
39struct SettingsView: View { var body: some View { Text("Settings") } }
40struct ProfileView: View { var body: some View { Text("Profile") } }
41struct HelpView: View { var body: some View { Text("Help") } }

This avoids conflicting boolean state and keeps modal routing centralized.

Dismiss and Trigger Next Sheet Safely

If one sheet action should open another, dismiss first, then set the next state asynchronously on the next run loop turn.

swift
1Button("Go to Help") {
2    activeSheet = nil
3    DispatchQueue.main.async {
4        activeSheet = .help
5    }
6}

This sequence prevents overlapping presentation calls.

Coordinate With Navigation and Alerts

SwiftUI views can also present alerts, confirmations, and navigation destinations. Centralizing modal state in a small coordinator object keeps interactions predictable.

swift
final class ModalCoordinator: ObservableObject {
    @Published var activeSheet: ActiveSheet?
}

Injecting this coordinator through environment object can simplify larger feature modules.

Preview and Test Modal Routing

As sheet logic grows, add focused previews and UI tests for each modal path. This catches regressions where a button updates state but no presentation appears because the host view changed unexpectedly.

swift
1struct ContentView_Previews: PreviewProvider {
2    static var previews: some View {
3        ContentView()
4    }
5}

For UI tests, tap each button and assert that expected text exists in the sheet content. Consistent test coverage helps prevent subtle routing breakage during refactors.

Common Pitfalls

A common mistake is stacking many boolean sheet flags and expecting deterministic behavior. Use one source of truth for modal presentation.

Another issue is trying to present a new sheet before the current one dismisses. Queue the next action after dismissal.

A third issue is placing .sheet deep in subviews that are conditionally created. If the host view disappears, presentation behavior becomes inconsistent.

Summary

  • Multiple .sheet(isPresented:) modifiers on one view can conflict.
  • Prefer one .sheet(item:) with an enum based state model.
  • Dismiss current sheet before presenting another.
  • Centralize modal routing for complex SwiftUI screens.
  • Keep presentation modifier on a stable, always mounted view.

Course illustration
Course illustration

All Rights Reserved.