在我的应用程序中,LazyVGrid
多次重新构建其内容。网格中的项目数可能不同或保持不变。每次必须以编程方式将特定项目滚动到视图中。
当第LazyVGrid
一个出现时,可以使用onAppear()
修饰符将项目滚动到视图中。
有什么方法可以检测LazyVGrid
下一次完成重新构建其项目的时刻,以便可以安全地滚动网格?
这是我的代码:
网格
struct Grid: View {
@ObservedObject var viewModel: ViewModel
var columns: [GridItem] {
Array(repeating: .init(.flexible(), alignment: .topLeading), count: viewModel.data.count / viewModel.rows)
}
var body: some View {
GeometryReader { geometry in
ScrollView {
ScrollViewReader { scrollViewProxy in
LazyVGrid(columns: columns) {
let rowsCount = viewModel.rows
let columsCount = columns.count
ForEach((0..<rowsCount*columsCount), id: \.self) { index in
let data = viewModel.getData(for: index)
Text(data)
.id(index)
}
}
.onAppear {
// Scroll a particular item into view
let targetIndex = 32 // an arbitrary number for simplicity sake
scrollViewProxy.scrollTo(targetIndex, anchor: .top)
}
.onChange(of: geometry.size.width) { newWidth in
// Available screen width changed, for example on device rotation
// We need to re-build the grid to show more or less columns respectively.
// To achive this, we re-load data
// Problem: how to detect the moment when the LazyVGrid
// finishes re-building its items
// so that the grid can be safely scrolled?
let availableWidth = geometry.size.width
let columnsNumber = ScreenWidth.getNumberOfColumns(width: Int(availableWidth))
Task {
await viewModel.loadData(columnsNumber)
}
}
}
}
}
}
}
帮助枚举确定要在网格中显示的列数
enum ScreenWidth: Int, CaseIterable {
case extraSmall = 320
case small = 428
case middle = 568
case large = 667
case extraLarge = 1080
static func getNumberOfColumns(width: Int) -> Int {
var screenWidth: ScreenWidth = .extraSmall
for w in ScreenWidth.allCases {
if width >= w.rawValue {
screenWidth = w
}
}
var numberOfColums: Int
switch screenWidth {
case .extraSmall:
numberOfColums = 2
case .small:
numberOfColums = 3
case .middle:
numberOfColums = 4
case .large:
numberOfColums = 5
case .extraLarge:
numberOfColums = 8
}
return numberOfColums
}
}
简化的视图模型
final class ViewModel: ObservableObject {
@Published private(set) var data: [String] = []
var rows: Int = 26
init() {
data = loadDataHelper(3)
}
func loadData(_ cols: Int) async {
// emulating data loading latency
await Task.sleep(UInt64(1 * Double(NSEC_PER_SEC)))
DispatchQueue.main.async { [weak self] in
if let _self = self {
_self.data = _self.loadDataHelper(cols)
}
}
}
private func loadDataHelper(_ cols: Int) -> [String] {
var dataGrid : [String] = []
for index in 0..<rows*cols {
dataGrid.append("\(index) Lorem ipsum dolor sit amet")
}
return dataGrid
}
func getData(for index: Int) -> String {
if (index > data.count-1){
return "No data"
}
return data[index]
}
}