I am currently attempting assignment 3 of stanford's 193p 2020 course. I am having trouble in trying to get my function createCardContent to actually display the Shape object it should be displaying. Instead of displaying a shape object on the cards, it is not displaying anything, which ends up just showing empty cards (and I don't know why) image here.
If I remove the text @ViewBuilder, then I get an error
Function declares an opaque return type, but has no return statements
in its body from which to infer an underlying type
If I add the return inside the if-else if statements, I get an error saying
Function declares an opaque return type, but the return statements in
its body do not have matching underlying types
struct CardView: View {
var card: SetGameEngine.Card
var body: some View {
GeometryReader { geometry in
self.testBody(for: geometry.size)
}
}
func testBody(for size: CGSize) -> some View {
ZStack {
HStack {
ForEach(0..<card.symbolCount) { _ in
self.createCardContent()
}
}
} .cardify()
}
@ViewBuilder
func createCardContent() -> some View {
let colour = self.colour()
let symbol = self.card.symbol
let shade = self.card.shade
if symbol == .oval, shade == .solid {
Capsule().fill(colour)
} else if symbol == .squiggle, shade == .solid {
Rectangle().fill(colour)
} else if symbol == .diamond, shade == .solid {
Diamond().fill(colour)
} else if symbol == .oval, shade == .empty {
Capsule().stroke(colour)
} else if symbol == .squiggle, shade == .empty {
Rectangle().stroke(colour)
} else if symbol == .diamond, shade == .empty {
Diamond().stroke(colour)
} else {
Capsule().fill(Color.pink)
}
}
func colour() -> Color {
switch self.card.colour {
case .red:
return Color.red
case .green:
return Color.green
case .purple:
return Color.purple
}
}
EDIT: This is very weird. When I remove the local variables I wrote in the function createCardContent and their references, the card content gets populated by the shapes I want. Here is my new function createCardContent. Can anyone explain to me why this is working but the previous function is not?
@ViewBuilder
func createCardContent() -> some View {
if card.symbol == .oval && card.shade == .solid {
Capsule().fill(colour())
} else if card.symbol == .squiggle && card.shade == .solid {
return Rectangle().fill(colour())
} else if card.symbol == .diamond && card.shade == .solid {
return Diamond().fill(colour())
} else if card.symbol == .oval && card.shade == .empty {
return Capsule().stroke(colour())
} else if card.symbol == .squiggle && card.shade == .empty {
return Rectangle().stroke(colour())
} else if card.symbol == .diamond && card.shade == .empty {
return Diamond().stroke(colour())
} else {
Diamond().stroke(colour())
}
}
toph42
(Topher Hickman)
2
You can’t put arbitrary code inside a @ViewBuilder but need each line (up to a max of ten) to return some View or be one of the few flow supported flow control options available (mostly just if statement with else also supported).
The @ViewBuilder attribute is actually just syntactic sugar wrapping a function that returns a TupleView of up to ten contained other Views.
1 Like
toph42
(Topher Hickman)
3
Sorry, I am not sure I explained it properly. The version with the constants defined at the start of the function didn’t work because each of those let statements are not a closure or a function that returns some View to wrap inside the TupleView that @ViewBuilder makes.
Apple’s documentation for ViewBuilder may help illustrate it better when you see the various factory methods it uses to turn your input into a View for SwiftUI to display.
Here’s the example that takes two arguments:
static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View
You can see that the two arguments are generic (what Prof. Hegarty calls “don’t cares”) but have a constraint that they must conform to the View protocol (so what Prof. Hegarty calls “cares a little bit”). Each line inside your @ViewBuilder must return something that conforms to View to be passed through these buildBlock factory methods and let colour = self.colour() is a statement that defines a constant, not one that returns some View, so putting it inside the ViewBuilder is invalid.
1 Like
Ahhhh! Thank you for explaining it so well! Appreciate is very much!!
1 Like
toph42
(Topher Hickman)
5
Also, since I didn’t address it in my earlier answer: The error you get without @ViewBuilder is because the opaque return type of some View has to be a type that the compiler can figure out what it actually is and you have runtime checks that change the concrete type that your function returns. Wrap the ifs in a Group { } block (you’re then beholden to the control flow restrictions, since Group is a ViewBuilder, so keep your constant and variable declarations outside the Group) and the compiler will be able to infer a specific return type.
young
(rtSwift)
6
Swift 5.3, let is now allowed in @ViewBuilder
must change to:
if symbol == .oval && shade == .solid
"," does not work in @ViewBuilder, must use &&
1 Like
toph42
(Topher Hickman)
7
I haven’t had a chance to play with 5.3. Do you know what else has changed? Is there an official list of new features somewhere? I heard you could use switch now, too.
Lantua
9
This seems to be the latest update regarding implementation.