Search code examples
swiftswiftuiframe

Get width of a view using in SwiftUI


I need to get width of a rendered view in SwiftUI, which is apparently not that easy.

The way I see it is that I need a function that returns a view's dimensions, simple as that.

var body: some View {
    VStack(alignment: .leading) {
        Text(timer.name)
            .font(.largeTitle)
            .fontWeight(.heavy)
        Text(timer.time)
            .font(.largeTitle)
            .fontWeight(.heavy)
            .opacity(0.5)
    }
}

Solution

  • The only available mechanism to get the dimension of a view, that is auto-resized by SwiftUI, is the GeometryReader. The GeometryReader is a proxy view that returns the dimensions of the container in which your view gets rendered.

    struct SomeView: View {
        
        @State var size: CGSize = .zero
        
        var body: some View {
            VStack {
                Text("VStack width: \(size.width)")
                Text("VStack height: \(size.height)")
                
                GeometryReader { proxy in
                    HStack {} // just an empty container to triggers the onAppear
                        .onAppear {
                            size = proxy.size
                        }
                }
            }
            
        }
    }
    

    The printed size is the dimension of the VStack.

    How to get the dimension of a View

    Now that we know that the GeometryReader gives us the size of the container, the usual follow-up question is: how do I use it to get the size of a specific view?

    To do this we need to move the geometry reader one level below our targeted view. How? We could add an empty background that gets the size of the targeted view and sends this information back to a Binding.

    Let's create a SizeCalculator ViewModifier so that we can use this functionality on every view:

    struct SizeCalculator: ViewModifier {
        
        @Binding var size: CGSize
        
        func body(content: Content) -> some View {
            content
                .background(
                    GeometryReader { proxy in
                        Color.clear // we just want the reader to get triggered, so let's use an empty color
                            .onAppear {
                                size = proxy.size
                            }
                    }
                )
        }
    }
    
    extension View {
        func saveSize(in size: Binding<CGSize>) -> some View {
            modifier(SizeCalculator(size: size))
        }
    }
    

    The job of the SizeCalculator is to add a GeometryReader as the background of our target view. On appear, so after SwiftUI has rendered the content, it will send the size back to the Binding.

    Usage:

    struct SomeView: View {
        
        @State var size: CGSize = .zero
        
        var body: some View {
            VStack {
                Text("text width: \(size.width)")
                Text("text height: \(size.height)")
                
                Text("hello")
                    .saveSize(in: $size)
            }
            
        }
    }
    

    how to get the size of a view in swiftUI