SwiftUI Tip: Using AnyShapeStyle
A quick tip that took me too long to realize and easy for someone new to SwiftUI to get stuck on. Say you have a piece of UI where you want to change the background depending on the state. In this case, we want to have no background (using .clear
) or relying on one of the built-in styles like .tertiary
or .background
or maybe a material like .thinMaterial
. Try this out with a ternary operator and you’ll get any number of errors that aren’t immediately clear about the cause.
Let’s try .tertiary
and we’ll get an error that it can’t be used on ShapeStyle
even though we know it can be used fine without the ternary operator
Let’s try another, here’s using .thinMaterial
and we get yet another confusing error
One more and I swear we’ll get to the point soon. Let’s try .background
Ah, yet another error, but this one is actually helpful! If you’ve spent much time in Swift, it’s probably obvious all along, but this a type problem. The .background()
modifier we’re using is expecting a ShapeStyle
. We have .clear
which a Color
, .tertiary
which is a HierarchicalShapeStyle
, .background
which is a BackgroundStyle
and .thinMaterial
is a Material
. They all individually conform to ShapeStyle
so they work correctly when used in isolation, or they will work when using the same concrete type (.e.g - two Colors or two Materials, etc), but they can’t be directly combined in a ternary.
You may have reached for a conditional like this or a modifier to do the same, but that’s not ideal as it complicates the view hierarchy and won’t work as expected for animations since those are two distinct views.
if isSelected {
Rectangle()
.background(.thinMaterial)
} else {
Rectangle()
.background(.clear)
}
The trivial fix here is to use AnyShapeStyle
to type-erase the underlying concrete types so they can be combined. All of these work as expected.
Rectangle()
.background(isSelected ? AnyShapeStyle(.thinMaterial) : AnyShapeStyle(.clear))
Rectangle()
.background(isSelected ? AnyShapeStyle(.tertiary) : AnyShapeStyle(.clear))
Rectangle()
.background(isSelected ? AnyShapeStyle(.background) : AnyShapeStyle(.clear))
That was a long-winded explanation for a one-line fix, but hopefully helpful if you’re new to SwiftUI when you see a similar problem in the future (and you will), you’ll know what tool to reach for. There are a number of other type-erasing wrappers you might need in other cases like AnyShape
or AnyGesture
when confronted with the same problem.