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!
I would suggest simplifying your ChildSizeReader
:
Use an .onChange
handler to detect when the size of the GeometryProxy
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.
struct ChildSizeReader<Content: View>: View {
@Binding var size: CGSize
let content: () -> Content
var body: some View {
content()
.background {
GeometryReader { proxy in
Color.clear
.onChange(of: proxy.size, initial: true) { oldVal, newVal in
size = newVal
}
}
}
}
}
An alternative way to implement the size reader is as a ViewModifier
. I added a new answer to the post you referenced, to show how it can work this way.