如何在列表顶部插入项目时保持LazyVStack / VStack中的滚动位置



我创建了一个包含要显示的项目的简单垂直列表。

问题是当我通过insertAtTop()在列表顶部插入项目时。滚动视图不会停留在同一位置。

观看视频:https://streamable.com/i7ywab


如何使滚动视图保持在同一位置?

下面是示例代码。

struct TestView: View {

@State private var items: [Item] = []

var body: some View {
NavigationView {
ScrollView {
LazyVStack {
ForEach(items, id: ._id) { item in
Text(item.text)
.background(Color.green)
.padding(.bottom, 10)
}
}
}
.navigationBarItems(trailing: HStack {
Button("[Insert at top]", action: {
insertAtTop()
})
Spacer(minLength: 20)
Button("[Load]", action: {
load()
})
})
.navigationTitle("Item List")
}
}

private func load() {
items = (1...50).map { _ in Item() }
}

private func insertAtTop() {
let newItems = (1...20).map { _ in Item() }
items.insert(contentsOf: newItems, at: 0)
}

}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
struct Item {
var _id = UUID()
var text = UUID().uuidString.prefix(5)
}

只有在列表顶部添加新视图时,才会出现问题。如果修改替换现有视图,则没有问题。

。因此,一种解决方法是在占位符数组上循环 ForEach,该数组在负方向上具有大量视图(例如 10000(。然后在 ForEach 中,检查索引以查看它是否存在于实数/非占位符数组中。

如果是这样 ->真实视图 如果不是 ->便宜的视图(例如,具有正确背景颜色且高度为 1 的 Rectangle((。

LazyVStack {
ForEach(placeholderIndices, id: .self) { index in
if indices.contains(index) {
REAL VIEW
} else {
Rectangle()
.fill(Color.bg)
.frame(height: 0)
}
}
}

展开视图时,占位符索引保持不变;只有"真实"索引会发生变化

我遇到了同样的问题,但决定采取另一种方法来解决它,只需将顶部元素id(我将项目插入到顶部(到 @State 在获取新项目包之前。也许它可以帮助将来的某人:)

@State private var lastId = -1
ScrollView {
ScrollViewReader { value in
LazyVStack {

ForEach(Array(items.enumerated()), id: .element) { index, item in

Element
.id(item.id)
.onAppear {
if (items.first == message) {
lastId = item.id
doFetch()
}
}

}
// listening to the changes in items list (the one I update) p.s. I have items in the viewmodel
.onChange(of: items, perform: { new in
// on first fetch I scroll to the bottom
if (lastId == -1) {
value.scrollTo(items.last?.id ?? 0)
} else {
// on any next I scroll to the last saved id
value.scrollTo(self.lastId)
}               
})       
}    
}
}

最新更新