iosswiftswiftuiscreenvstack

SWIFTUI How to capture VStack height with geometry (after the VStack was displayed)?


I'd like to ask you some help. I'm trying to capture VStack height using geometry and then based on that VStack height value, calculate its child element's height (inside VStack).

Image of my current view of VStack

I used .frame outside of VStack to fill the whole screen. Then I used .border to visually check if it actually fills the screen (it works fine)

.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)

The problem can be seen inside the VStack, the height is displayed as GCFloat 734. Even though the border size is a lot bigger.

struct BodyView: View {
    @State var stackHeight : CGFloat = 0

    var body: some View {
        GeometryReader { geometry in
            VStack {
                Text("VStack size: \(self.stackHeight)")
                    .bold()
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding(.horizontal)
                    .background(Color.green)

            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
            .onAppear{
                self.stackHeight = geometry.size.height
            }
            .border(Color.purple, width: 5)
        }
    }
}

struct BodyView_Previews: PreviewProvider {
    static var previews: some View {
        BodyView()
    }
}

How could I capture the actual size of the VStack when it finishes loading?
When I trigger (onAppear) the VStack height appears correct, however I need it to be captured instantly.


Solution

  • If I understand your questions, you're very close — you just need to use to GeometryProxy type:

    import SwiftUI
    
    struct BodyView: View {
    
        var body: some View {
            GeometryReader { geometry in
                VStack {
                    Text("VStack size: \(geometry.size.height)") // no need for state var anymore
                        .bold()
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(.horizontal)
                        .background(Color.green)
                }
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
                .border(Color.purple, width: 5)
            }
        }
    }
    
    struct BodyView_Previews: PreviewProvider {
        static var previews: some View {
            BodyView()
        }
    }
    

    A GeometryReader will lay itself out again if its size changes.