.ultraThinMaterial doesn't work in ternary operator

I'm trying to make the background of a view use the .ultraThinMaterial property with the following code:

.background(!isPlayerOpen ? .ultraThinMaterial : Color.clear)

isPlayerOpen returns either true or false.

However when I try and build this, I get the following inline errors:

  • Member 'ultraThinMaterial' in 'Color' produces result of type 'Material', but context expects 'Color'
  • Static property 'ultraThinMaterial' requires the types 'Color' and 'Material' be equivalent

Is there a way to fix this? I find this strange because the following works without issue:

.background(.ultraThinMaterial)

So why would a ternary operator break this?

The background modifier overload being used there is:

func background<S>(_ style: S, ignoresSafeAreaEdges edges: Edge.Set = .all) -> some View where S : ShapeStyle

This means it takes a specific ShapeStyle type as generic, and must be statically deterministic. But the ternary expression does not provide a same-type enforcement, it will vary based on the value of isPlayerOpen — it will either be a Material or a Color. This means it cannot be statically type-checked and you get the error.

In the second case, there is no conditional so the type is always fixed as Material, which is compatible with the ShapeStyle generic constraint.

The first problem is perhaps more understandable if you break it out into a standalone function, where the static types are more explicitly visible. Your case is roughly equivalent to doing:

The error produced here is a bit clearer at explaining the problem, compared to what the compiler came up with when everything was originally inlined.

So, how do you solve this? Well, SwiftUI provides a type-erased AnyShapeStyle that you can use to have dynamically different underlying shape styles based on a condition. The fix therefore is to write:

.background(!isPlayerOpen ? AnyShapeStyle(.ultraThinMaterial) : AnyShapeStyle(.clear))

By wrapping the properties like this, the return type of the condition is always AnyShapeStyle so the compiler is satisfied as the generic parameter is always resolved to the same type.

6 Likes

Thank you for your answer here, that now makes more sense to me.

No problem. For what it's worth, this is a bit unintuitive — and the ceremony of AnyShapeStyle is not ideal in my view. Hopefully, eventually, the Swift language will expand in functionality to be able to not need those explicit type-erased wrappers (instead handled implicitly by an any ShapeStyle conformance or some similar approach).

Yeah it's confusing; I've come to learn that Swift cares a lot about types which has been tricky coming from a language like php that couldn't care less :joy:

It just wants the same type for both branches. Same here:

Bool.random() ? 42 : "42" // Error