In the following code, I know
body
is a computed property which only have a get
part.return
is omitted for VStack
but what's { .. }
after Vstack? is this Initialization or closures?
and what's inside {}, nothing return i think.
struct ContentView: View {
var body: some View {
VStack {
MapView()
.frame(height: 300)
CircleImage()
.offset(y: -130)
.padding(.bottom, -130)
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
.padding()
Spacer()
}
}
}
HStack positions views in a horizontal line, VStack positions them in a vertical line, and ZStack overlays views on top of one another.
VStack allows to arrange its child views in a vertical line, and ZStack allows to overlap its child views on top of each other. Stacks can further be customized with alignment and spacing in order to modify their appearance.
A view that arranges its children in a vertical line.
If you look at the docs for VStack.init
, you'll see that the last argument it accepts is indeed a closure. The magic here is that the closure is marked with @ViewBuilder
.
@ViewBuilder
is a kind of function builder. The idea is that you pass in a closure containing a bunch of expressions, and then the function builder will combine those expressions into a single value. It's kind of like returning an array, but in an arguably better-looking syntax. (it's not really an array though. The return type of the closure is decided by the function builder.)
In your code, you are returning an "array" of 4 views.
MapView
CircleImage
VStack
Spacer
These will get passed to the ViewBuilder
, and it combines all of them into a single View
object.
And if you are wondering what the methods called on at the end of each view are doing, they are just methods that return slight modifications of the objects on which they are called. For example padding
returns the same view, but with some padding applied.
When you look at the signature of VStack
initializer:
public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
You can see that it takes 3 arguments, the first two have default values so they can be omitted (as in your example). The last one is a ViewBuilder
and has no default value, so needs to be provided.
When you look at a definition of a ViewBuilder
it takes between 0 and 10 Views:
static func buildBlock() -> EmptyView
static func buildBlock<Content>(Content) -> Content
static func buildBlock<C0, C1>(C0, C1) -> TupleView<(C0, C1)>
static func buildBlock<C0, C1, C2>(C0, C1, C2) -> TupleView<(C0, C1, C2)>
/....
static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>
Long story short, what you see between { .. } is a ViewBuilder
, which is a closure, that is part of the initializer.
The kind of VStack
syntax is possible thanks to a new feature in Swift 5.1 called Function Builder.
Without function builders, the code would look like that:
var body: some View {
var builder = VStackBuilder()
builder.add(Image(uiImage: image))
builder.add(Text(title))
builder.add(Text(subtitle))
return builder.build()
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With