0

TL;DR: my app has some global alerts and modals (e.g. hints about trial limits, the in-app purchase screens, etc.) which should be available through out the app. In UIKit I could always present alerts or modals on the topmost view controller, no matter what was currently presented. In SwiftUI, .alert and .sheet are tied to the local view hierarchy, so triggering an alert from inside a sheet dismisses the sheet first.

Is there a SwiftUI-native pattern for presenting global alerts/sheets/modals on top of everything—without UIKit bridging and without rebuilding custom alert UIs?


I’m looking for a way in SwiftUI to show app-wide alerts or sheets, regardless of where the user currently is in the view hierarchy.

In UIKit this was simple: I just walked up the presentedViewController chain and called present(...) on the topmost view controller.

In SwiftUI, this approach doesn’t translate because .alert and .sheet, etc. belong to the currently visible view hierarchy.

class AppController: ObservableObject {
    @Published var isShowingAlert = false
    @Published var isShowingSheet = false
    @Published var isShowingMsg = false

    func showAlert() { isShowingAlert = true }
    func showSheet() { isShowingSheet = true }
    func showMsg() { isShowingMsg = true }
}

struct RootView: View {
    @StateObject var appController = AppController()

    var body: some View {
        VStack {
            Button("Show Alert") { appController.showAlert() }
            Button("Show Sheet") { appController.showSheet() }
            Button("Show Msg") { appController.showMsg() }
        }
        .alert("Title", isPresented: $appController.isShowingAlert) { }
        .sheet(isPresented: $appController.isShowingSheet) {
            SubViewA(appController: appController)
        }

        // additional modal
        .sheet(isPresented: $appController.isShowingMsg) {
            Text("Message")
        }
    }
}

struct SubViewA: View {
    @ObservedObject var appController: AppController

    var body: some View {
        VStack {
            Text("SubViewA")

            // Will dissmiss SubViewA-Sheet and show alert on RootView
            Button("Show Alert") { appController.showAlert() }

            // Fails
            Button("Show Msg") { appController.showMsg() }
        }
    }
}

If the alert is triggered inside the presented sheet (SubViewA), SwiftUI first dismisses the .sheet and only then shows the alert. So I cannot display a “global” alert on top of an existing .sheet. Showing another .sheet (or .fullscreenCover) over the existing sheet also fails.

I’m looking for a pure SwiftUI solution that:

  • has no fallback to UIKit
  • allows global alert/sheet presentation
  • shows them above currently presented sheets
  • does not rely on UIKit (UIViewController, present(...))
  • does not require fully custom alert implementations

Is there a recommended, SwiftUI-native pattern for global, hierarchy-independent modal presentation? Or is this simply not possible with SwiftUI’s current presentation system?

0

1 Answer 1

1

I would strongly suggest not to rely on the top-most view controller and to instead present such alerts in separate UIWindows.

This will cover both for UIKit and SwiftUI, and you won't have to worry about what is the top-most view [controller], whether it is capable of presenting another view controller without introducing potential layout issues, and whether such presentation would work at all given that a view controller cannot present another view controller if it's already presenting some other view controller.

If you've opted into UIScenes, you'd have to instantiate a UIWindow using a UIWindowScene. Otherwise, you'll be able to instantiate a UIWindow just using a frame parameter.

And just a note here, since your alerts are quite important to be presented: if a user is screen-sharing, AirPlaying, etc you'll have more than one UIWindowScene. If you don't ensure you're using the right scene, your alert might only appear on an external monitor, with your users freely using the app on another UIScene instance.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the import. I will keep this in mind. However this does not answer the question, wether there is a pure / recommended SwiftUI solution to solve this problem.
No, there is no pure nor recommended SwiftUI solution to solve this problem (AFAIK). Neither was there in UIKit, to be precise, but we all figured this to be the cleanest and safest approach. EDIT: There actually might be a way to port this window approach to SwiftUI, but I haven't attempted that yet.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.