LazyVStack - 行 onAppear 被提前调用
LazyVStack - row onAppear is called early
我有一个 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 必须 'preload' 它们。
有没有办法解决这个问题,或者如果这是有意为之,我如何才能准确知道最后一个 visible 项(接受不同的行高)?
编辑
LazyVStack
的 documentation 状态:
The stack is “lazy,” in that the stack view doesn’t create items until it needs to render them onscreen.
所以我想这一定是一个错误?
根据documentation的话,onAppear
不应该是这样的:
The stack is “lazy,” in that the stack view doesn’t create items until it needs to render them onscreen.
但是,如果您在使它正常工作时遇到问题,请参阅下面我的解决方案。
虽然我不确定为什么 onAppear
行会提前触发,但我已经创建了一个变通解决方案。这会读取滚动视图边界的几何形状和要跟踪的单个视图,比较它们,并设置它是否可见。
在此示例中,当最后一项的上边缘在滚动视图的边界中可见时,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()
}
}
它按照我上面的评论工作。
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) }
}
}
}
}
}
我有一个 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 必须 'preload' 它们。
有没有办法解决这个问题,或者如果这是有意为之,我如何才能准确知道最后一个 visible 项(接受不同的行高)?
编辑
LazyVStack
的 documentation 状态:
The stack is “lazy,” in that the stack view doesn’t create items until it needs to render them onscreen.
所以我想这一定是一个错误?
根据documentation的话,onAppear
不应该是这样的:
The stack is “lazy,” in that the stack view doesn’t create items until it needs to render them onscreen.
但是,如果您在使它正常工作时遇到问题,请参阅下面我的解决方案。
虽然我不确定为什么 onAppear
行会提前触发,但我已经创建了一个变通解决方案。这会读取滚动视图边界的几何形状和要跟踪的单个视图,比较它们,并设置它是否可见。
在此示例中,当最后一项的上边缘在滚动视图的边界中可见时,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()
}
}
它按照我上面的评论工作。
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) }
}
}
}
}
}