我创建了一个包含要显示的项目的简单垂直列表。
问题是当我通过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)
}
})
}
}
}