Is there a global Alert View in SwiftUI?

I am writing a SwiftUI based app, and errors can occur anywhere. I've got a function that logs the error.

But it would be nice to be able to call an Alert Msg, no matter where I am, then gracefully exits the app.

Sure I can right the alert into every view, but that seems ridiculously unnecessary.

Am I missing something?

Answered by VAndrJ in 834636022

You can add an alert to the highest view in the current view tree hierarchy and pass it on, for example, via Environment.

Briefly, one of the options:

extension EnvironmentValues {
    @Entry var alertError: Binding<Error?> = .constant(nil)
}

enum Destination {
    case screen2
    case screen3
}

struct ContentView: View {
    @State private var presentedError: Error? = nil

    var body: some View {
        NavigationStack {
            Screen1()
                .navigationDestination(for: Destination.self) { destination in
                    switch destination {
                    case .screen2: Screen2()
                    case .screen3: Screen3()
                    }
                }
        }
        .environment(\.alertError, $presentedError)
        .alert(
            "Error!",
            isPresented: .constant(presentedError != nil),
            presenting: presentedError,
            actions: { _ in
                Button("Everything is lost :(") {
                    presentedError = nil
                }
            },
            message: { error in
                Text(error.localizedDescription)
            }
        )
    }
}

struct Screen1: View {
    @Environment(\.alertError) private var presentedError

    var body: some View {
        VStack {
            Text("Screen1")
            NavigationLink("Next", value: Destination.screen2)
                .padding()
            Button("Trigger Error") {
                presentedError.wrappedValue = MyAwesomeError.screen1
            }
        }
    }
}

struct Screen2: View {
    @Environment(\.alertError) private var presentedError

    var body: some View {
        VStack {
            Text("Screen2")
            NavigationLink("Next", value: Destination.screen3)
            Button("Trigger Error") {
                presentedError.wrappedValue = MyAwesomeError.screen2
            }
        }
    }
}

struct Screen3: View {
    @Environment(\.alertError) private var presentedError

    var body: some View {
        VStack {
            Text("Screen3")
            Button("Trigger Error") {
                presentedError.wrappedValue = MyAwesomeError.screen3
            }
        }
    }
}

enum MyAwesomeError: Error, LocalizedError {
    case screen1
    case screen2
    case screen3

    var errorDescription: String? { String(describing: self) }
}

You can add an alert to the highest view in the current view tree hierarchy and pass it on, for example, via Environment.

Briefly, one of the options:

extension EnvironmentValues {
    @Entry var alertError: Binding<Error?> = .constant(nil)
}

enum Destination {
    case screen2
    case screen3
}

struct ContentView: View {
    @State private var presentedError: Error? = nil

    var body: some View {
        NavigationStack {
            Screen1()
                .navigationDestination(for: Destination.self) { destination in
                    switch destination {
                    case .screen2: Screen2()
                    case .screen3: Screen3()
                    }
                }
        }
        .environment(\.alertError, $presentedError)
        .alert(
            "Error!",
            isPresented: .constant(presentedError != nil),
            presenting: presentedError,
            actions: { _ in
                Button("Everything is lost :(") {
                    presentedError = nil
                }
            },
            message: { error in
                Text(error.localizedDescription)
            }
        )
    }
}

struct Screen1: View {
    @Environment(\.alertError) private var presentedError

    var body: some View {
        VStack {
            Text("Screen1")
            NavigationLink("Next", value: Destination.screen2)
                .padding()
            Button("Trigger Error") {
                presentedError.wrappedValue = MyAwesomeError.screen1
            }
        }
    }
}

struct Screen2: View {
    @Environment(\.alertError) private var presentedError

    var body: some View {
        VStack {
            Text("Screen2")
            NavigationLink("Next", value: Destination.screen3)
            Button("Trigger Error") {
                presentedError.wrappedValue = MyAwesomeError.screen2
            }
        }
    }
}

struct Screen3: View {
    @Environment(\.alertError) private var presentedError

    var body: some View {
        VStack {
            Text("Screen3")
            Button("Trigger Error") {
                presentedError.wrappedValue = MyAwesomeError.screen3
            }
        }
    }
}

enum MyAwesomeError: Error, LocalizedError {
    case screen1
    case screen2
    case screen3

    var errorDescription: String? { String(describing: self) }
}

But this would require I am using something like NavigationStacks, correct? I'm not using those. One view calls another and so on.

Is there a global Alert View in SwiftUI?
 
 
Q