1
votes

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
You are trying to present alert on viewA, while viewB is already presented. which sounds incorrect, what exactly you want? You can just use the Bool value from contentView and make a separate alert in viewB on button click, based on bool state.Tushar Sharma
@TusharSharma Thanks for replying. Actually my condition is that I want to show Alert in entire app from one place. Let suppose I have n number of Views in my app there available model sheets also. If I'm in one of model sheet and my alert in very first view of the app. If it tiggers when I’m in the sheet. I want to display that alert. I hope you understand my condition.Shriram Asava
@TusharSharma, Actually I want to find the root view of the app show that can display the alert from anywhere. Currently in above condition the Sheet follow different view hierarchy and ContentView follow different hierarchy so alert is not displayed on sheet.Shriram Asava
so your alert trigger will fire from the root view and you want to display this alert to any view?Raja Kishan
@RajaKishan, Currently In my app I added this alert in very first view of my app in WindowGroup { }. It works in app but once any sheet is open in the current state that alert is not displayed. Whenever that alert is tigger I want to show that alert in the app even in the sheet also.Shriram Asava

2 Answers

0
votes

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")
            }
        }
    }
}
0
votes

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, in your @main struct
@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!")
        }
    }
}