I\'ve been seeing some strange behavior for preference keys with ScrollView. If I put the onPreferenceChange
inside the ScrollView
it won\'t be cal
The problem here is actually not in ScrollView
but in usage - this mechanism allow to transfer data up in viewTree:
A view with multiple children automatically combines its values for a given preference into a single value visible to its ancestors.
source
The keywords here - with multiple children
. This mean that u can pass it in viewTree from child to parent.
Let's review u'r code:
struct ContentView: View {
var body: some View {
ScrollView {
Text("Hello")
.preference(key: WidthPreferenceKey.self, value: 20)
.onPreferenceChange(WidthPreferenceKey.self) {
print($0) // Not being called, we're in a scroll view.
}
}
}
}
As u can see now - child pass value to itself, and not to parent - so this don't want to work, as per design.
And working case:
struct ContentView: View {
var body: some View {
ScrollView {
Text("Hello")
.preference(key: WidthPreferenceKey.self, value: 20)
}
.onPreferenceChange(WidthPreferenceKey.self) {
print($0)
}
}
}
Here, ScrollView
is parent and Text
is child, and child talk to parent - everything works as expected.
So, as I sad in the beginning the problem here not in ScrollView but in usage and in Apple documentation (u need to read it few times as always).
And regarding this:
Bound preference
WidthPreferenceKey
tried to update multiple times per frame.
This is because u may change multiply values in same time and View can't be rendered, try to .receive(on:)
or DispatchQueue.main.async
as workaround (I guess this may be a bug)