I am trying to get the size of a ScrollView’s content on the initial render of the View, but it is returning 0.0 instead of the actual size. I created a ChildSizeReader
View following this post to try to achieve this, and the code looks like this:
ChildSizeReader:
struct ChildSizeReader<Content: View>: View {
@Binding var size: CGSize
let content: () -> Content
var body: some View {
ZStack {
content()
.background(
GeometryReader { proxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: proxy.size)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) { sizeValue in
size = sizeValue
}
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value _: inout Value, nextValue: () -> Value) {
_ = nextValue()
}
}
Use of ChildSizeReader:
@State private var scrollViewSize: CGSize = .zero
...
ScrollViewReader { proxy in
ScrollView(.vertical) {
ChildSizeReader(size: $scrollViewSize) {
FeatureSet(features: features)
}
}
}
FeatureSet:
struct FeatureSet: View {
var features: [Feature]
var body: some View {
LazyVStack {
ForEach(Array(features.enumerated()), id: .element.title) { index, feature in
FeatureRow(title: feature.title, value: feature.value)
.id(feature.title)
if index != features.count - 1 {
Divider()
.frame(height: 1)
.overlay(Color(hex: 0x808080, opacity: 0.2))
}
}
}
}
}
I did also try wrapping Color.clear.preference
in the conditional statement if proxy.size != .zero
such as seen in this forum, but the size was still 0.0 when assigned to size
.
But, the moment that I start scrolling, the size is set to the expected value.
Are there any ideas on how to retrieve the correct size of the ScrollView
on the initial render? Thank you!
2
Answers
The implementation of the preference key is incorrect. You never assigned anything to
value
inreduce
, so it remains at the default value of.zero
.Here is a correct implementation:
Note that I changed the value of the preference to be optional, so as to satisfy the invariant documented here. Namely,
reduce(value: &x, nextValue: {defaultValue})
does not change the meaning ofx
.(Since
Color.clear
does not have any siblings, this is probably not necessary, but it’s good to follow the documentation.)In
onPreferenceChange
, unwrap the optional:I would suggest simplifying your
ChildSizeReader
:Use an
.onChange
handler to detect when the size of theGeometryProxy
changes.Supply
initial: true
to the.onChange
handler, so that it also reads the size on initial show (this is the same as having an.onAppear
callback doing the same thing).A
PreferenceKey
is not needed.The
ZStack
is not needed either.