swift LazyVStack -提前调用行onAppear

nsc4cvqm  于 2023-03-11  发布在  Swift
关注(0)|答案(4)|浏览(131)

我有一个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声明:
堆栈是“惰性”的,因为堆栈视图直到需要在屏幕上呈现项目时才创建项目。
所以我猜这一定是个窃听器?

xxb16uws

xxb16uws1#

documentation的话说,onAppear不应该是这样的:
堆栈是“惰性”的,因为堆栈视图直到需要在屏幕上呈现项目时才创建项目。
但是,如果您有问题,让这个正常工作,请参阅我的解决方案如下。
虽然我不确定为什么onAppear s行被提前触发,但是我已经创建了一个变通的解决方案,读取滚动视图边界的几何形状和要跟踪的单个视图,比较它们,并设置它是否可见。
在本例中,当滚动视图的边界中最后一个项目的上边缘可见时,isVisible属性会发生变化。由于安全区域的原因,当它在屏幕上可见时,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()
    }
}
rlcwz9us

rlcwz9us2#

它的工作原理和我上面的评论一样。

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) }
                }
            }
        }
    }
}
o2g1uqev

o2g1uqev3#

这看起来难以置信,但只要添加一个包含ScrollView的GeometryReader就可以解决这个问题

GeometryReader { _ in
        ScrollView(.vertical, showsIndicators: false) {
            LazyVStack(spacing: 14) {
                Text("Items")
                LazyVStack(spacing: 16) {
                    ForEach(viewModel.data, id: \.id) { data in
                        MediaRowView(data: data)
                        .onAppear {
                            print(data.title, "item appeared")
                        }
                    }
                    if viewModel.state == .loading {
                        ProgressView()
                    }
                }
            }
            .padding(.horizontal, 16)
        }
    }
wrrgggsh

wrrgggsh4#

在我的例子中,问题是我把我的LazyVStack放在ScrollView中,而ScrollView又放在另一个ScrollView中(很容易失去它的踪迹)。

相关问题