Do I need VStack in body for SwiftUI

Viewed 96

I have the following code:

  var body: some View {
    VStack {
      contentView
        .padding()
      
      ToolBarView()
        .padding()
    }
  }

I noticed that if I remove VStack, the code still works. This is very strange. Are they equivalent? Is VStack here implicitly used? If I keep this VStack, does it mean that SwiftUI will generate duplicate nested VStack?

Edit:

The contentView is:

  var contentView: some View {
    let state = stateObservable.state
    if state.isResultPage {
      return AnyView(ResultContentView())
    } else {
      return AnyView(JokeContentView())
    }
  }
1 Answers

To avoid needing to define a ToolBarView, I'm going to use this example instead of yours:

struct MyView: View {
    var body: some View {
        Text("first")
        Text("second")
    }
}

So, what is the actual type returned by MyView().body? Let's ask Swift:

; xcrun swift
Welcome to Swift version 5.5.2-dev.
Type :help for assistance.
  1> import SwiftUI
  2> struct MyView: View { 
  3.     var body: some View { 
  4.         Text("first") 
  5.         Text("second") 
  6.     } 
  7. }
  8> print(type(of: MyView().body))
TupleView<(Text, Text)>

It's TupleView<(Text, Text)>. So what, exactly, is a TupleView? The documentation says it's

A View created from a swift tuple of View values.

…That doesn't really explain much. Allow me to explain what TupleView is really all about.

SwiftUI uses TupleView to represent a collection of views, without imposing any specific layout on them. The layout is determined entirely by the container of the TupleView (or in this case of the MyView). If you always want a vertical layout, you need to use VStack explicitly. If you want the container to pick the layout, don't use VStack.

If we put the MyView in a VStack, we get a vertical layout.

PlaygroundPage.current.setLiveView(VStack {
    MyView()
})

Result:

first above second

But if we put the MyView in an HStack then we get a horizontal layout.

PlaygroundPage.current.setLiveView(HStack {
    MyView()
})

Result:

first to the left of second

Or we can the MyView in a ControlGroup and get a different horizontal layout:

PlaygroundPage.current.setLiveView(ControlGroup {
    MyView()
})

Result:

first to the left of second with a divider between them, both Texts dimmed

If we put the MyView inside a ZStack, the Texts are overlaid.

PlaygroundPage.current.setLiveView(ZStack {
    MyView()
})

Result:

first and second overlaid

If we put the MyView inside a GeometryReader, the Texts are again overlaid.

PlaygroundPage.current.setLiveView(GeometryReader { proxy in
    MyView()
}.frame(width: 100, height: 100))

Result:

first and second overlaid at the top left of a white 100 point square

If we use MyView as the body of a PreviewProvider, then we get two entirely separate previews.

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView()
    }
}

Result:

a preview of first above a preview of second

Related