Reputation: 12992
I'm trying to understand how to change @State
variables in @ViewBuilder
closures. The following is just a simple example:
struct ContentView: View {
@State var width = CGFloat(0)
var body: some View { //Error 1
GeometryReader { geometry in //Error 2
self.width = geometry.size.width
return Text("Hello world!")
}
}
}
I'm getting some errors:
Error 1:
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
But the return
is redundant because there's only one line of code inside the View
computed property.
Error 2:
Cannot convert return expression of type 'GeometryReader<_>' to return type 'some View'
Since I explicitly write return Text("...")
shouldn't the type be clear?
What's the problem here?
Upvotes: 0
Views: 2101
Reputation: 4513
First, you can't make arbitrary swift statements in a functionBuilder
. There is a specific syntax allowed. In SwiftUI's case, it's a ViewBuilder
, which under the hood is composing your type. When you make consecutive statements in SwiftUI, you are actually relying on the compiler to compose a new type underneath according to the rules of that DSL.
2nd, SwiftUI is a recipe, it's not an event system. You don't change state variables in the body
function, you set up how things should react when state variables change externally. If you want another view to react in some way to that width, you need to define that content with respect to the width of the component you want. Without knowing what your ultimate goal is here, it's hard to answer how to relate the content to each other.
EDIT:
I was asked to elaborate on what exactly is allowed. Each functionBuilder
has a different allowable syntax which is defined in the functionBuilder
itself. This has a good overview on function builders in Swift 5.1: https://www.swiftbysundell.com/posts/the-swift-51-features-that-power-swiftuis-api
As for what SwiftUI is specifically looking for, it's essentially looking for each statement to return an instance of View
.
// works fine!
VStack {
Text("Does this")
Text("Work?")
}
// doesn't work!
VStack {
Text("Updating work status...")
self.workStatus = .ok // this doesn't return an instance of `View`!
}
// roundabout, but ok...
VStack {
Text("Being clever")
gimmeView()
}
// fine to execute arbitrary code now, as long as we return a `View`
private func gimmeView() -> some View {
self.workingStatus = .roundabout
return Text("Yes, but it does work.")
}
This is why you got the obtuse error you got:
Cannot convert return expression of type 'GeometryReader<_>' to return type 'some View'
The type system can't construct any View
out of View
and essentially Void
when you execute:
self.width = geometry.size.width
When you do consecutive View
statements, underneath, it's still being converted into a new type of View
:
// the result of this is TupleView<Text, Text>
Text("left")
Text("right")
Upvotes: 5