0

In SwiftUI, foregroundStyle(_:) seems to do some @environment magic, but it's also not accessible through any @Environment(\.insertMagicKeyHere) key that I can find, so I have been trying to make my own version of it to apply to some custom views.

However, in the view, when I try to apply the custom view, it gives me an error:

@Environment(\.elementStyle) var elementStyle

var body: some View {
    CustomShape()
        .foregroundStyle(elementStyle) //Type 'any ShapeStyle' cannot conform to 'ShapeStyle'
}

I believe I understand why this is an issue, but I don't know what the magic sauce is to allow my modifier to accept any kind of ShapeStyle. I've tried any number of different generics or whatnot, but I just can't figure out for the life of me how to store an object that conforms to ShapeStyle for use in filling the shape.

How does Apple do it? Is there some kind of magic I don't understand? For reference, my EnvironmentKey is provided below. I would love to not have to write a different modifier/key for every single kind of ShapeStyle. There has to be a way to do this that I'm not seeing, right?

struct ElementStyleKey: EnvironmentKey {
    static var defaultValue: any ShapeStyle = .primary
}

extension EnvironmentValues {
    var elementStyle: any ShapeStyle {
        get { self[ElementStyleKey.self] }
        set { self[ElementStyleKey.self] = newValue }
    }
}

struct ElementStyleModifier: ViewModifier {
    var style: any ShapeStyle
    func body(content: Content) -> some View {
        content
            .environment(\.elementStyle, style)
    }
}

extension View {
    public func elementStyle(_ style: any ShapeStyle) -> some View {
        modifier(ElementStyleModifier(style: style))
    }
}

1 Answer 1

5

There is the AnyShapeStyle type eraser that you can use as the type of the environment value.

struct ElementStyleKey: EnvironmentKey {
    static var defaultValue: AnyShapeStyle = AnyShapeStyle(.primary)
}

extension EnvironmentValues {
    var elementStyle: AnyShapeStyle {
        get { self[ElementStyleKey.self] }
        set { self[ElementStyleKey.self] = newValue }
    }
}

struct ElementStyleModifier: ViewModifier {
    var style: AnyShapeStyle
    func body(content: Content) -> some View {
        content
            .environment(\.elementStyle, style)
    }
}

extension View {
    public func elementStyle<S: ShapeStyle>(_ style: S) -> some View {
        modifier(ElementStyleModifier(style: AnyShapeStyle(style)))
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you. AnyShapeStyle was the special sauce. I had a feeling it would be something easy I didn't think of.
_ForegroundStyleModifier does put the style in the environment, but it uses an internalEnvironmentKey. You can dump the entire environment by adding a @Environment(\.self) var env property to a view and then adding let _ = print(env) to the view's body.

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.