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)
}
}
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
.
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)
}
}
}