简体   繁体   中英

How to Show Alert from Anywhere in app SwiftUI?

I have condition to show alert in a view which can able to show from anywhere in the app. Like I want to present it from root view so it can possibly display in all view. Currently what happens when I present from very first view it will display that alert until i flow the same Navigation View. Once any sheets open alert is not displayed on it. Have any solutions in SwiftUI to show alert from one place to entire app.

Here is my current Implementation of code. This is my contentView where the sheet is presented and also alert added in it.

 struct ContentView: View {
    @State var showAlert: Bool = false
    @State var showSheet: Bool = false
    var body: some View {
        NavigationView {
            Button(action: {
                showSheet = true
            }, label: {
                Text("Show Sheet")
            }).padding()
            .sheet(isPresented: $showSheet, content: {
                SheetView(showAlert: $showAlert)
            })
        }
        .alert(isPresented: $showAlert, content: {
            Alert(title: Text("Alert"))
        })
    }
}

Here from sheet I am toggle the alert and the alert is not displayed.

 struct SheetView: View {
    @Binding var showAlert: Bool
    var body: some View {
        Button(action: {
            showAlert = true
        }, label: {
            Text("Show Alert")
        })
    }
}

here is the error in debug when we toggle button

AlertDemo[14187:3947182] [Presentation] Attempt to present <SwiftUI.PlatformAlertController: 0x109009c00> on <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x103908b50> (from <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x103908b50>) which is already presenting <_TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView_: 0x103d05f50>.

Any solution for that in SwiftUI? Thanks in Advance.

I was able to achieve this with this simplified version of what @workingdog suggested in their answer. It works as follows:

  1. create the Alerter class that notifies the top-level and asks to display an alert
class Alerter: ObservableObject {
    @Published var alert: Alert? {
        didSet { isShowingAlert = alert != nil }
    }
    @Published var isShowingAlert = false
}
  1. render the alert at the top-most level, for example in your @main struct or the ContentView
@main
struct MyApp: App {
    @StateObject var alerter: Alerter = Alerter()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(alerter)
                .alert(isPresented: $alerter.isShowingAlert) {
                    alerter.alert ?? Alert(title: Text(""))
                }
        }
    }
}
  1. set the alert that should be displayed from inside a child view
struct SomeChildView: View {

    @EnvironmentObject var alerter: Alerter
    
    var body: some View {
        Button("show alert") {
            alerter.alert = Alert(title: "Hello from SomeChildView!")
        }
    }
}

Note on sheets

If you present views as sheets, each sheet needs to implement its own alert, just like MyApp does above.
If you have a NavigationView inside your sheet and present other views within this navigation view in the same sheet, the subsequent sheets can use the first sheet's alert, just like SomeChildView does in my example above.

Here is a possible example solution to show an Alert anywhere in the App. It uses "Environment" and "ObservableObject".

import SwiftUI

@main
struct TestApp: App {
    @StateObject var alerter = Alerter()
    
    var body: some Scene {
        WindowGroup {
            ContentView().environment(\.alerterKey, alerter)
                .alert(isPresented: $alerter.showAlert) {
                    Alert(title: Text("This is the global alert"),
                          message: Text("... alert alert alert ..."),
                          dismissButton: .default(Text("OK")))
                }
        }
    }
}

struct AlerterKey: EnvironmentKey {
    static let defaultValue = Alerter()
}

extension EnvironmentValues {
    var alerterKey: Alerter {
        get { return self[AlerterKey] }
        set { self[AlerterKey] = newValue }
    }
}

class Alerter: ObservableObject {
    @Published var showAlert = false
}

struct ContentView: View {
    @Environment(\.alerterKey) var theAlerter
    var body: some View {
        NavigationView {
              VStack {
                  NavigationLink(destination: SecondView()) {
                      Text("Click for second view")
                  }.padding(20)
                Button(action: { theAlerter.showAlert.toggle()}) {
                    Text("Show alert here")
                }
              }
          }.navigationViewStyle(StackNavigationViewStyle())
    }
}

struct SecondView: View {
    @Environment(\.alerterKey) var theAlerter
    var body: some View {
        VStack {
            Button(action: { theAlerter.showAlert.toggle()}) {
                Text("Show alert in second view")
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM