34

I have the following code:

@State private var signoutAlert = false

var body: some View {

    Button(action: {
      
        self.signoutAlert = true
        
        print("signout button clicked")
        
    }) {
        
        Text("Sign Out")
    
    }
    .alert(isPresented: $signoutAlert) {
        
        print(".alert will display")
        //
        return Alert(title: Text("Sign Out"), message: Text("Are you sure you want to Sign Out?"), primaryButton: .destructive(Text("Sign Out")) {
            
            print("Signing out....")
          
            self.session.signOut()

            self.presentationMode.wrappedValue.dismiss()
            
        }, secondaryButton: .cancel())
    
    }

}

The following output prints out:

  1. signout button clicked
  2. .alert will display

I'm expecting the Alert Box to display and prompt the user to either "Cancel" or "Sign Out" by click one of the two buttons; but it is never displayed or prompts the user, which makes no sense!?

Does anyone see anything wrong with my code!? This is extremely frustrating since it should be very simple!?

1
  • Works fine with Xcode 12. Commented Jul 6, 2020 at 2:52

5 Answers 5

39

I've had this problem when there is an .alert(...) view modifier on a view further up the view hierarchy. If a parent view has an .alert() defined on it then a child's .alert() modifier will not display it's alert.

In my case I moved the .alert() on the high level view down to be a modifier on the Button that triggered it, and now both alerts display correctly. For example:

struct HiddenWalrusView: View {
    @State private var showAlert1 = false
    @State private var showAlert2 = false
    
    var body: some View {
        VStack {
            Button("Show Alert One") { showAlert1 = true }
                .padding(20)
            Button("Show Alert Two") { showAlert2 = true }
                .padding(20)
                .alert(isPresented: $showAlert2) {
                    // This alert never displays
                    Alert(title: Text("I am the Walrus"))
                }
        }
        .alert(isPresented: $showAlert1) {
            Alert(title: Text("I am the Egg Man"))
        }
    }
}

struct EggmanAndWalrusView: View {
    @State private var showAlert1 = false
    @State private var showAlert2 = false
    
    var body: some View {
        VStack {
            Button("Show Alert One") { showAlert1 = true }
                .padding(20)
                .alert(isPresented: $showAlert1) {
                    Alert(title: Text("I am the Egg Man"))
                }
            Button("Show Alert Two") { showAlert2 = true }
                .padding(20)
                .alert(isPresented: $showAlert2) {
                    Alert(title: Text("I am the Walrus"))
                }
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Hi, do you have any idea why the Alert need to be defined in the containing view of the buttom ? I though that the Alert is unbound to the view because it appears on a different window.
Complementing this reply: Set the state on your parent view that you already have another .alert set and pass that value to the child through a binding property. That way on the child view you can toggle the value of your binding and it will present the alert on the parent.
Had this problem with a context menu on mac, had to move the alert on the same view as the context menu. However when i dismiss the alert it appears again, only on a second dismiss disappears. Guess this api is deprecated for a reason...
17

I had the same problem but realized this is now a deprecated .alert modifier. When I moved to using the newer modifier, it worked fine. Example from Apple's documentation:

        .alert(title, isPresented: $didFail) {
            Button("OK") {
                // Handle acknowledgement.
            }
        } message: {
            Text("Please ensure your credentials are correct.")
        }

3 Comments

'alert(_:isPresented:actions:message:)' is only available in iOS 15.0 or newer
Works like a charm. Yes its iOS 15 and above and it looks like Apple fixed this issue finally.
Here is the link to Apple's documentation: developer.apple.com/documentation/swiftui/view/… It also explains how to add different button handlers with specific roles to the alert view.
12

Thanks to @ken-chin-purcell, his answer helps me to find more convenient workaround.

Just put alert under EmptyView:

    .overlay(
        EmptyView()
            .alert(isPresented: $showAlert1) {
                Alert(
                    title: ...,
                    message: ...,
                    ...
                )
            },
        alignment: .bottomTrailing
    )
    .overlay(
        EmptyView()
            .alert(isPresented: $showAlert2) {
                Alert(
                    title: ...,
                    message: ...,
                    ...
                )
            },
        alignment: .bottomTrailing
    )

Or use extension:

extension View {
    public func alertPatched(isPresented: Binding<Bool>, content: () -> Alert) -> some View {
        self.overlay(
            EmptyView().alert(isPresented: isPresented, content: content),
            alignment: .bottomTrailing
        )
    }
}

1 Comment

This is awesome! However, why the need for the .bottomTrailing alignment?
2

I want to provide another approach just in case someone still gets this issue and tried all different approaches mentioned on this entry.

Create an enum for your alerts, example:

enum AlertTypes {
    case AlertTypeOne, AlertTypeTwo
}

Use one .alert modifier to handle your alerts on your view with a switch case to show depending on the situation.

.alert(isPresented: self.$showAlert) {
        
        switch alertType {
        case .AlertTypeOne:
            Alert(title: Text("Alert One"))
        case .AlertTypeTwo:
            Alert(title: Text("Alert One"))
        }
        
 }

Don't forget the properties for this to work:

@State private var showAlert             = false
@State private var alertType: AlertTypes = .AlertTypeOne

Finally on your button or view with tap gesture:

self.alertType = .AlertTypeOne // or AlertTypeTwo 
self.showAlert = true

This is a little hacky, not super dry but I was able to make it work after tried all options, so just leaving here my notes in case someone finds it useful.

Comments

0

Along with changing the Alert initializer to a non-deprecated one according to this answer, I also had to use Text("") instead of attaching the alert to an EmptyView.

Text("")
    .alert(title, isPresented: $isAlertPresented) {
        Button("OK") {
            print("Button pressed")
        } 
    }

Comments

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.