Why isn't onPreferenceChange being called if it's inside a ScrollView in SwiftUI?

前端 未结 5 2060
逝去的感伤
逝去的感伤 2021-01-02 02:57

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

5条回答
  •  南方客
    南方客 (楼主)
    2021-01-02 03:20

    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)

提交回复
热议问题