Obtaining SwiftUI view size (height/width) with GeometryReader not returning correct values

84 Views Asked by At

I have the following code:

struct TestView: View {
    var body: some View {
            VStack {
                Spacer()
                    .frame(height: 50)
                
                GeometryReader { geo in
                    HStack(alignment: .top) {
                        VStack {
                            circleButton()
                            circleButton()
                            circleButton()
                        }
                        .background(.orange)
                        .coordinateSpace(name: "3Buttons")
                        
                        Spacer()
                        
                        let _ = print("GLOBAL: x: \(geo.frame(in: .global).origin.x), y: \(geo.frame(in: .global).origin.y), width: \(geo.frame(in: .global).width), height: \(geo.frame(in: .global).size.height)\n")
                        let _ = print("LOCAL: x: \(geo.frame(in: .local).origin.x), y: \(geo.frame(in: .local).origin.y), width: \(geo.frame(in: .local).width), height: \(geo.frame(in: .local).size.height)\n")
                        let _ = print("3Buttons: x: \(geo.frame(in: .named("3Buttons")).origin.x), y: \(geo.frame(in: .named("3Buttons")).origin.y), width: \(geo.frame(in: .named("3Buttons")).width), height: \(geo.frame(in: .named("3Buttons")).height)\n")
                        let _ = print("2Buttons: x: \(geo.frame(in: .named("2Buttons")).origin.x), y: \(geo.frame(in: .named("2Buttons")).origin.y), width: \(geo.frame(in: .named("2Buttons")).width), height: \(geo.frame(in: .named("2Buttons")).height)\n")
                        
                        VStack {
                            circleButton()
                            circleButton()
                        }
                        .background(.red)
                        .coordinateSpace(name: "2Buttons")
                    }
                }
            }
        Spacer()
    }
    
    func circleButton() -> some View {
        Button {
            print()
        } label: {
            Circle()
                .fill(.blue)
                .frame(width: 40, height: 40)
        }
    }
}

Which is producing the following outcome:

Image showing 3 circle buttons left, 50 points inset from the top, with total approximate height of 150ish on an orange background

I'm trying to obtain both origin x & y relative to its parent [i.e. the red background view should likely have origin of say (600, 0) with size (40, 100)], as well as the view/backgrounds size [say (40, 100)].

Printing out the various named coordinateSpaces, .global, and .local... all return the same height, width, etc. The resulting printouts are as follows:

GLOBAL: x: 0.0, y: 109.0, width: 393.0, height: 701.0

LOCAL: x: 0.0, y: 0.0, width: 393.0, height: 701.0

3Buttons: x: 0.0, y: 109.0, width: 393.0, height: 701.0

2Buttons: x: 0.0, y: 109.0, width: 393.0, height: 701.0

It doesn't seem to matter where I place the geometry reader or the printouts.

1

There are 1 best solutions below

3
On

Ok, I possibly have answered my own question. As I understand it now, GeometryReader expands to fill the space where it's used.

Using it in either

a) .background and returning .clear color, or

b) .overlay [Although I've not tried this]

will return the appropriate size.

Sample code:

A:

Button {
    print()
} label: {
    Text("Button")
}
.background {
    GeometryReader { geo in
        Color.clear
        let _ = print("Button size: \(geo.size)")
    }
}

This prints:

Button size: (68.0, 20.333333333333332)

I suspect there is a better way though, so this should not be the accepted answer. This seems like a workaround that was likely necessary at some point, but has likely been replaced by a more appropriate solution or will be soon... one would think.