感谢您帮助我解决以下问题。
总体目标
我在一个 SwiftUI 视图上工作,它包含一个可滚动的区域来呈现一些数据。假设它应该显示一定范围内的自然数,例如 0 到 2,000,000。每个数字都显示在跨越一半屏幕的自定义数字视图中。可滚动区域包含按升序排列的数量视图。
约束如下:
- 数字视图是延迟加载的。
- 我们可以定义加载可滚动区域时首先显示的数字视图。
- 在某些时候,可滚动视图必须忘记超出范围的视图以释放内存。前两个约束强加:我们可以加载视图并显示第 1,000,000 个数字视图,而无需加载之前的 999,999 个数字视图。
第一约束
满足第一个约束很简单。我们创建一个ScrollView
嵌套 a LazyVStack
, aForEach
以使数据可用和数字视图。如果我们显示我们的视图,打印语句只显示前几个视图被加载。
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
LazyVStack {
ForEach(0..<2000000) { number in
NumberView(number)
}
}
}
}
struct NumberView: View {
let number: Int
init(_ number: Int) {
self.number = number
print("init: \(number)")
}
var body: some View {
print("\(number)")
return Text("\(number)")
.frame(height: 300)
.border(.red)
}
}
第二个约束
我们修改之前的代码。因此,我们将 嵌入ScrollView
到 aScrollViewReader
中并用 ID 标记数字视图。现在,一旦出现,我们就可以滚动到所需的数字视图ScrollView
。
var body: some View {
ScrollViewReader { proxy in
ScrollView(.vertical, showsIndicators: false) {
LazyVStack {
ForEach(0..<2000000) { number in
NumberView(number)
.id(number)
}
}
}
.onAppear {
proxy.scrollTo(1000000, anchor: .top)
}
}
}
但是现在我们违反了我们的第一个约束。当滚动到第 1000000 个数字视图时,我们必须初始化之前的 999,999 个。这有两个缺点:它需要大量的时间和内存。最终导致内存分配失败。也许,取消分配数字视图是一种选择。但是我们仍然必须初始化它们以便以后丢弃它们,这需要时间。
第三个约束
我不能 100% 确定 SwiftUI 是否或在什么情况下会在无法访问的情况下取消分配 number-view-structs。onDisappear
我假设一旦调用了它们的方法,它们就可以从内存中删除。
替代解决方案
我读了一篇关于无限滚动列表的文章。这篇文章没有解决完全相同的问题。但它建议ForEach
根据显示的数字视图修改迭代列表。onAppear
我们可以使用和来跟踪显示的数字视图onDisappear
。我们可以使用这两个函数来更新列表中加载的数字。
struct InfiniteList: View {
@StateObject var state: Data = Data()
var body: some View {
ScrollView {
LazyVStack {
ForEach(state.list, id: \.self) { number in
NumberView(number)
.onAppear {
// load more data
}
.onDisappear {
// unload data
}
}
}
}
}
}
class Data: ObservableObject {
// list that holds the nearby numbers
@Published var list: [Int] = [1000000]
}
但是一旦我们更新列表,ScrollView 就会显示最小数字的数字视图,触发它的onAppear
功能,因此会加载更小的数字。
我认为我们可以使最后一种方法起作用:
- 禁用更新号码列表
- 滚动到实际视图
- 再次启用更新
但这会很复杂,我们必须滚动用户在更新之前看到的确切位置。