0

I'm not sure if this is possible, but I'm trying to use a protocol function to populate the actions parameter of an .alert modifier in SwiftUI, and I can't figure out exactly how to do it correctly.

The code I'm trying goes like this:

protocol ErrorWithActions: Error {
    func alertActions<V: View>() -> V
}

struct MyView: View {
    @State var isShowingError = false
    var theError: ErrorWithActions? = nil

    var body: some View {
        Text("Blah blah blah")
            .alert(
                "An Error Occurred",
                isPresented: $isShowingError,
                presenting: theError,
                actions: { $0.alertActions() } // Compile error here
            )
    }
}

This won't compile because the actions: { $0.alertActions() } line fails with the compiler error Generic parameter 'Content' could not be inferred.

I think I understand that this doesn't work because .alert's ViewBuilder parameters need to know at compile time what the resulting view will be, and the protocol implementation can't supply that (without the protocol having an associatedtype for its view output, but that would require all cases of an enum implementing the protocol to return the same set of actions, which defeats the purpose of the protocol function).

I implemented a workaround that seems to work, where instead of alertActions() returning a view, it returns an array of AlertAction, a struct that can be translated into the buttons used in the alert, but it would be really nice if I could just supply the buttons directly. Is there a way to do that?

2
  • 1
    You are never going to be able to use a Protocol in place of a View struct as a it is never an unsubstantiated object. From Swift.org, "A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol." Commented Jun 30, 2023 at 14:30
  • Oh well. As I suspected. I guess I’ll stick with the workaround. Thanks! Commented Jun 30, 2023 at 18:42

1 Answer 1

1

You can use AnyView instead of making it through generics.

This will work:

protocol ErrorWithActions: Error {
    func alertActions() -> AnyView
}

Also, it's unrelated, but instead of @Published you are meant to use @State for isShowingError

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

3 Comments

Thank you! AnyView that did the trick perfectly (just have to remember to wrap any return value in AnyView(...), but that's easy enough). And you're right about the @Published. I copied that wrong from my code. Fixed now.
AnyView has performance implications and is generally not something you want to rely on much. Watch Demystify SwiftUI from WWDC for information about why.
Apple does indeed recommend to avoid AnyView as it erases structural identification and makes the view to rebuild if there are any changes. However, in this specific case, I wouldn't expect actions for the error to be very dynamic and be observing any state changes, so it should be acceptable here.

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.