【问题标题】:LazyVStack - row onAppear is called earlyLazyVStack - 行 onAppear 被提前调用
【发布时间】:2022-01-05 21:32:06
【问题描述】:

我有一个LazyVStack,有很多行。代码:

struct ContentView: View {
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(0 ..< 100) { i in
                    Text("Item: \(i + 1)")
                        .onAppear {
                            print("Appeared:", i + 1)
                        }
                }
            }
        }
    }
}

最初在屏幕上只能看到大约 40 行,但 onAppear 会触发 77 行。为什么会这样,为什么在屏幕上真正可见之前调用它?我不明白为什么 SwiftUI 必须“预加载”它们。

有没有办法解决这个问题,或者如果这是有意的,我怎样才能准确地知道最后一个可见项目(接受不同的行高)?

编辑

LazyVStackdocumentation 声明:

堆栈是“惰性的”,因为堆栈视图在需要将它们呈现在屏幕上之前不会创建项目。

那么我想这一定是一个错误?

【问题讨论】:

  • 如果有任何帮助,我无法使用 XCode 13.1 和 iPhone 13 Pro Max 模拟器(显示 39 行)重现它。当视图加载时,打印了 39 条记录,有点随机顺序,一旦滚动开始,额外的记录就会按顺序打印。
  • @tromgy 啊,我正在使用 Xcode 13.2.0 beta 2 :/ 这真的是一个 beta 问题吗?啊????我会在 13.1 上试一试
  • @tromgy 不-尝试复制相同的确切条件。最初仍然打印太多,不确定有什么不同。即使在真实设备上,它仍然存在相同的问题:/
  • @George:如果您将 100 更改为 1000 并且在第一次滚动后您会发现它工作得很好。
  • @swiftPunk 不,不适合我。它仍然被称为早约 39 行 - 这意味着它必须以某种方式将它认为的可见区域增加一倍

标签: swift swiftui lazyvstack


【解决方案1】:

documentation的话来说,onAppear不应该是这样的:

堆栈是“惰性的”,因为堆栈视图在需要将它们呈现在屏幕上之前不会创建项目。

但是,如果您在使其正常工作时遇到问题,请参阅下面的解决方案。


虽然我不确定为什么会提前触发行onAppears,但我已经创建了一个变通解决方案。这会读取滚动视图边界的几何形状和要跟踪的单个视图,比较它们,并设置它是否可见。

在此示例中,当最后一项的上边缘在滚动视图的边界中可见时,isVisible 属性会发生变化。由于安全区域,这可能不会在屏幕上可见,但您可以根据需要进行更改。

代码:

struct ContentView: View {
    @State private var isVisible = false

    var body: some View {
        GeometryReader { geo in
            ScrollView {
                LazyVStack {
                    ForEach(0 ..< 100) { i in
                        Text("Item: \(i + 1)")
                            .background(tracker(index: i))
                    }
                }
            }
            .onPreferenceChange(TrackerKey.self) { edge in
                let isVisible = edge < geo.frame(in: .global).maxY

                if isVisible != self.isVisible {
                    self.isVisible = isVisible
                    print("Now visible:", isVisible ? "yes" : "no")
                }
            }
        }
    }

    @ViewBuilder private func tracker(index: Int) -> some View {
        if index == 99 {
            GeometryReader { geo in
                Color.clear.preference(
                    key: TrackerKey.self,
                    value: geo.frame(in: .global).minY
                )
            }
        }
    }
}
struct TrackerKey: PreferenceKey {
    static let defaultValue: CGFloat = .greatestFiniteMagnitude

    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = nextValue()
    }
}

【讨论】:

    【解决方案2】:

    它按照我上面的 cmets 工作。

    struct ContentView: View {
        var body: some View {
            ScrollView {
                LazyVStack {
                    ForEach(0 ..< 100) { i in
                        Text("Item: \(i + 1)")
                            .id(i)
                            .frame(width: 100, height: 100)
                            .padding()
                            .onAppear { print("Appeared:", i + 1) }
                    }
                }
            }
        }
    }
    

    【讨论】:

    • 您使用的是什么版本的 Xcode?我试过 13.1 和 13.2 beta 2
    • Xcode 13.1。我认为这不是 XCode 版本的问题。
    • 即使使用您拥有的确切代码,它仍然对我不起作用,所以我知道为什么它在我的机器上不起作用。我询问了 Xcode 版本,因为 SwiftUI 的行为经常在版本之间发生变化
    • @George 我已将代码添加到答案中,我看不出这在您的机器上是如何工作的。
    猜你喜欢
    • 2020-11-14
    • 1970-01-01
    • 1970-01-01
    • 2012-02-01
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 2021-01-09
    相关资源
    最近更新 更多