So I've seen some approach to read the CGRect
of a swiftui view using a technique where you setup a background
containing a GeometryReader
that is used to read the size of the view the background is set up. My issue is that this doesn't work if the view you're setting the background to has some if
s in it for example. See the code below for reference:
import SwiftUI
struct RectPreferenceKey: PreferenceKey {
static var defaultValue = CGRect.zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
struct ChildRectReaderModifier: ViewModifier {
@Binding var rect: CGRect
func body(content: Content) -> some View {
content
.background(
GeometryReader { proxy in
Color.clear.preference(
key: RectPreferenceKey.self,
value: proxy.frame(in: .global)
)
}
)
.onPreferenceChange(RectPreferenceKey.self) { preferences in
if preferences != .zero && self.rect != preferences {
self.rect = preferences
}
}
}
}
extension View {
func childRectReader(_ rect: Binding<CGRect>) -> some View {
modifier(ChildRectReaderModifier(rect: rect))
}
}
struct ContentView: View {
@State var showOptionalText = false
@State var rect = CGRect.zero
var body: some View {
VStack {
Text("\(rect.width)")
Button {
showOptionalText.toggle()
} label: {
Text("Toggle")
}
VStack {
Text("Hello, world!")
Text("Hello, world!")
if showOptionalText {
Text("Hello, world!")
}
Text("Hello, world!")
}
.childRectReader($rect)
}
}
}
In the example above, the printed width is saying 0
unless I remove the if
inside the inner VStack
Any idea why this happens? or any potential fixes?
CodePudding user response:
As it is used in view modifier the preference is called several times on stack so reduce is active, but it is not correct here (just assign last one overriding all previous).
As I see for this scenario the fix is (tested with Xcode 13.4 / iOS 15.5)
struct RectPreferenceKey: PreferenceKey {
static var defaultValue = CGRect.zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
// just remove anything from here !!
}
}
but if you wanted to handle multiple usage of this modifier for real views in same hierarchy then you have to use different model for this preference key (array, dictionary).