13

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.

8
  • 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. Commented May 17, 2021 at 10:37
  • @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. Commented May 17, 2021 at 11:02
  • @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. Commented May 17, 2021 at 11:02
  • so your alert trigger will fire from the root view and you want to display this alert to any view? Commented May 17, 2021 at 11:19
  • @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. Commented May 17, 2021 at 11:31

2 Answers 2

13

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.

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

2 Comments

This looks good to me, although unfortunately I can't get the sheet alert working properly. When implemented as you mentioned above, I can get the alert to display successfully on the homepage, then display successfully on the sheet page, but I can't get it to display successfully again on the home page after dismissing the sheet. Any tips?
Are you using separate instances of the alerter for 1) home and 2) sheet?
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")
            }
        }
    }
}

2 Comments

This was already implemented in my app but this condition also fails when you try to tigger alert in any sheet.
sheets are modal, so as far as I know you will have to put a separate Alert into it to make it work.

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.