skip to Main Content

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.

2

Answers


  1. 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")
                }
            }
        }
    }
    
    Login or Signup to reply.
  2. 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: Text("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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search