Hello dear community!
I'm a new to SwiftUI and going through the official Develop in Swift Tutorials.
I've just finished the chapter Customize views with properties
where it was show that it's possible to declare computed properties like this:
var iconName: String {
if isRainy {
return "cloud.rain.fill"
} else {
return "sun.max.fill"
}
}
And then use it as follows:
var body: some View {
VStack {
Text(day)
Image(systemName: iconName)
}
}
This is really cool and I liked it and I decided that I want to do the similar thing for the project from the previous chapter (ChatBubble), so I decided to declare the following property in my custom ChatBubble view:
struct ChatBubble: View {
let text: String
let color: Color
let isEllipsis: Bool
var shape: Shape {
if isEllipsis {
Ellipse()
} else {
RoundedRectangle(cornerRadius: 8)
}
}
var body: some View {
Text(text)
.padding()
.background(color, in: shape)
}
}
But first, I got a warning where I declare shape: Shape
that it must be written any Shape
like this:
var shape: any Shape { …
But in both cases I got a error in the body
:
'buildExpression' is unavailable: this expression does not conform to 'View'
What does it mean? Why I can't use the computed property with shape like the computed property for String?
Thank you very much in advance.
Hey there, welcome to the forums and SwiftUI!
You seem to have come across a common problem in SwiftUI when dealing with protocols, like Shape
.
You're telling Swift that the shape
property is some type that conforms to Shape
, but the problem is that Ellipse
and RoundedRectangle
are two different concrete types — even though they both conform to Shape
.
Swift needs to know the exact concrete type at compile time, so you can't return two different shape types from the same computed property unless you erase the type.
Swift suggests using any Shape
, like this:
var shape: any Shape {
if isEllipsis {
Ellipse()
} else {
RoundedRectangle(cornerRadius: 8)
}
}
But this approach doesn't work directly with modifiers like background(_:in:)
, because SwiftUI's view builder needs a specific Shape
type, not a generic any Shape
.
The solution is to use AnyShape
– a type-erased shape value – and can be used by wrapping each Shape
type like this:
var shape: some Shape {
if isEllipsis {
AnyShape(Ellipse())
} else {
AnyShape(RoundedRectangle(cornerRadius: 8))
}
}
Now Swift can infer the concrete type as AnyShape
, whilst also preserving the underlying shape's behaviour (like how it draws itself). That gives you flexibility without breaking Swift's strict type system.
You could also move the logic into the body and inline the condition using a ternary operator instead, like this:
.background(color, in: isEllipsis ? AnyShape(Ellipse()) : AnyShape(RoundedRectangle(cornerRadius: 8)))
// You can use the shorter syntax for creating shapes if you want to
.background(color, in: isEllipsis ? AnyShape(.ellipse) : AnyShape(.rect(cornerRadius: 8)))
I hope this explanation helps and also fixes your problem.