So I use the GeometryReader
to read the available width of a container. Then I calculate how large the buttons should be and that decides the true size. However the GeometryReader seem to not be aware of the size of its children.
How should you tell GeometryReader about its true size?
My guess is:
import SwiftUI
struct ContentView: View {
@State private var containerHeight: Double = 0.0
var body: some View {
GeometryReader { proxy in
Text("Test")
.task { containerHeight = calculateCustomHeight(using: proxy) }
}
.frame(height: containerHeight)
}
}
This works. However I am not really sure if this is the SwiftUI way or an infinite cycle.
The container height is calculated, which updates @State
, which updates the views, which calculates the container height, which updates @State
, etc.
I presume it works now because the value is the same, thus does not trigger another update of the view.
I assume you would like to obtain the size value of a view and work with it later. This can be accomplished by utilizing Preferences.
PreferenceKey
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
PreferenceKey
Implement the PreferenceKey
and make use of GeometryReader
on the desired View to extract its size.
.background {
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self,
value: geometry.size)
}
}
.onPreferenceChange(SizePreferenceKey.self) { size in
print("size of the red text is: \(size)")
// use the value however you want
sizeOfText = size
}
The entire code then looks like this:
struct GeometryReaderTest: View {
@State private var sizeOfRedText: CGSize = .zero
var body: some View {
VStack {
Text("Green text")
.background { Color.green }
Text("Red text")
.background { Color.red }
// PreferenceKey & GeometryReader usage
.background {
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self,
value: geometry.size)
}
}
}
.onPreferenceChange(SizePreferenceKey.self) { size in
print("size of the red text is: \(size)")
// use the value however you want
sizeOfRedText = size
}
}
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
You can create an extension
to conveniently extract the size of different views within your project.
extension View {
func getSizeOfView(_ getSize: @escaping ((CGSize) -> Void)) -> some View {
return self
.background {
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self,
value: geometry.size)
.onPreferenceChange(SizePreferenceKey.self) { value in
getSize(value)
}
}
}
}
}
Then you can call the getSizeOfView
like this:
Text("Red text")
.background { Color.red }
.getSizeOfView { size in
print("size of the red text is: \(size)")
sizeOfRedText = size
}