如果 onPreferenceChange 在 SwiftUI 中的 ScrollView 内,为什么不调用它?

McG*_*ire 16 swiftui

我一直在使用 ScrollView 看到首选项键的一些奇怪行为。如果我把它放在onPreferenceChange里面ScrollView它不会被调用,但如果我把它放在外面它会!


我已经设置了一个宽度首选项键,如下所示:

struct WidthPreferenceKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat(0)

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}
Run Code Online (Sandbox Code Playgroud)

以下简单视图不会打印:

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.
                }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但这有效:

struct ContentView: View {
    var body: some View {
        ScrollView {
            Text("Hello")
                .preference(key: WidthPreferenceKey.self, value: 20)
        }
        .onPreferenceChange(WidthPreferenceKey.self) {
            print($0)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道我可以使用后一种方法来解决这个问题,但有时我在一个无法访问其父滚动视图的子视图中,但我仍然想记录一个首选项键。

关于如何onPreferenceChange在 a 中被调用的任何想法ScrollView

注意:Bound preference WidthPreferenceKey tried to update multiple times per frame.当我将函数放入滚动视图时,我得到了,这可能会解释发生了什么,但我无法弄清楚。

谢谢!

Kyo*_*ang 15

我一直试图找出这个问题很长一段时间并找到了如何处理它,尽管我使用的方式只是一种解决方法。

与标志一起使用onAppearScrollView使其子项出现。

...
@State var isShowingContent = false
...

ScrollView {
    if isShowingContent {
        ContentView()
    }
}
.onAppear {
     self.isShowingContent = true
}
Run Code Online (Sandbox Code Playgroud)

或者,

List它代替。
它具有滚动功能,您可以在 UI 方面使用自己的功能和 UITableView 外观对其进行自定义。最重要的是它按我们的预期工作。

[如果你有时间阅读更多]
让我谈谈我对这个问题的看法。

我已经证实,onPreferenceChange没有在所谓的自举里面放一个视图的时间ScrollView。我不确定这是否是正确的行为。但是,我认为这是错误的,因为ScrollView即使其中一些使用 PreferenceKey 在其中的视图之间传递任何数据,也必须能够包含任何视图。如果这是正确的行为,我们在创建自定义视图时很容易遇到麻烦。

让我们更详细地了解一下。
我想这ScrollView与其他容器视图(例如 List、(H/V)Stack)在引导时设置其子视图时的工作方式略有不同。换句话说,ScrollView会尝试以自己的方式绘制(或布置)孩子。不幸的是,这种方式会影响孩子的布局机制,正如我们所看到的那样不正确地工作。我们可以猜测调试视图中的以下消息发生了什么。

TestHPreferenceKey tried to update multiple times per frame.

这可能是一个证据,告诉我们在ScrollView为它的设置做一些事情时发生了孩子的更新。此时,可以猜测到 PreferenceKey 的更新已被忽略。

这就是为什么我试图将放置子视图放到onAppear.

我希望这对那些在 SwiftUI 上遇到各种问题的人有用。


E.C*_*oms 2

transformPreference你可能只能在superView中读取它,但是你可以在设置后更改它。

struct ContentView: View {
var body: some View {
    ScrollView {
        VStack{
        Text("Hello")
            .preference(key: WidthPreferenceKey.self, value: 20)
        }.transformPreference(WidthPreferenceKey.self, {
        $0 = 30})
    }.onPreferenceChange(WidthPreferenceKey.self) {
        print($0)
    }
}
}
Run Code Online (Sandbox Code Playgroud)

最后一个值是30现在。希望这是你想要的。

您可以从其他层读取:

  ScrollView {

        Text("Hello").preference(key: WidthPreferenceKey.self, value: CGFloat(40.0))
            .backgroundPreferenceValue(WidthPreferenceKey.self) { x -> Color in
               print(x)
                return Color.clear
        }

    }
Run Code Online (Sandbox Code Playgroud)