SwiftUI中单个ScrollView嵌套两个LazyVGrid的滚动跳转问题
ScrollView嵌套两个动态加载LazyVGrid时滚动跳转问题
问题详情
同一ScrollView内包含两个LazyVGrid,均实现动态加载逻辑:
- 滚动至第一个网格底部时加载下一页,直至加载完100条内容
- 第一个网格加载完成后,滚动至第二个网格底部时加载其内容
问题现象:滚动第二个网格内容时,ScrollView会突然跳回第一个网格的末尾位置,推测是第一个LazyVGrid的尺寸计算出现异常。
环境:iOS 16.4、Xcode 15.4
复现代码
import SwiftUI struct ContentView: View { @State private var primaryItems = [Int]() @State private var secondaryItems = [Int]() @State private var primaryLoading = false @State private var secondaryLoading = false var body: some View { ScrollView { LazyVGrid(columns: [GridItem(alignment: .topLeading)], alignment: .leading, spacing: .zero, pinnedViews: .sectionHeaders) { Section(header: header) { ForEach(primaryItems, id: \.self) { item in cellView(width: UIScreen.main.bounds.size.width) } Color.clear .frame(height: 1) .onAppear { loadMorePrimaryItems() } } } LazyVGrid(columns: [GridItem(spacing: 5, alignment: .topLeading), GridItem(alignment: .topLeading)], alignment: .leading, spacing: .zero) { ForEach(secondaryItems, id: \.self) { item in cellView(isSecondary: true, width: (UIScreen.main.bounds.size.width - 5) / 2) } Color.clear .frame(height: 1) .onAppear { loadMoreSecondaryItems() } } .background(Color.gray) } } var header: some View { Text("This is a header") .frame(width: UIScreen.main.bounds.size.width, height: 100) .background(Color.red) } func cellView(isSecondary: Bool = false, width: CGFloat) -> some View { VStack(spacing: .zero) { (isSecondary ? Color.blue : Color.green) .frame(width: width, height: 200) Text("Hello, world!") } } func loadMorePrimaryItems() { guard !primaryLoading && primaryItems.count < 100 else { return } primaryLoading = true DispatchQueue.main.asyncAfter(deadline: .now() + 1) { let startIndex = primaryItems.count let endIndex = min(startIndex + 20, 100) let newItems = Array(startIndex..<endIndex) primaryItems.append(contentsOf: newItems) primaryLoading = false // Start loading secondary items if primary items reached 100 if primaryItems.count == 100 { loadMoreSecondaryItems() } } } func loadMoreSecondaryItems() { guard !secondaryLoading, primaryItems.count == 100 else { return } secondaryLoading = true DispatchQueue.main.asyncAfter(deadline: .now() + 1) { let startIndex = secondaryItems.count let newItems = Array(startIndex..<startIndex+20) secondaryItems.append(contentsOf: newItems) secondaryLoading = false } } }
问题演示

解决方案
方案1:设置第一个LazyVGrid的布局优先级
给第一个LazyVGrid添加.layoutPriority(1),让SwiftUI优先计算其布局,避免第二个网格加载时干扰其高度计算,这是最简洁的修复方式:
LazyVGrid(columns: [GridItem(alignment: .topLeading)], alignment: .leading, spacing: .zero, pinnedViews: .sectionHeaders) { // 原有内容 } .layoutPriority(1) // 添加此行
方案2:使用ScrollViewReader稳定滚动位置
通过ScrollViewReader记录当前滚动位置,在加载内容后手动恢复,避免位置跳转:
@State private var lastScrollID: String? = nil var body: some View { ScrollViewReader { proxy in ScrollView { // 第一个LazyVGrid给底部标记添加ID LazyVGrid(columns: [GridItem(alignment: .topLeading)], alignment: .leading, spacing: .zero, pinnedViews: .sectionHeaders) { Section(header: header) { ForEach(primaryItems, id: \.self) { item in cellView(width: UIScreen.main.bounds.size.width) } Color.clear .frame(height: 1) .id("primary-bottom") .onAppear { loadMorePrimaryItems() } } } // 第二个LazyVGrid加载时记录并恢复位置 LazyVGrid(columns: [GridItem(spacing: 5, alignment: .topLeading), GridItem(alignment: .topLeading)], alignment: .leading, spacing: .zero) { ForEach(secondaryItems, id: \.self) { item in cellView(isSecondary: true, width: (UIScreen.main.bounds.size.width - 5) / 2) } Color.clear .frame(height: 1) .onAppear { // 记录当前可见位置的ID lastScrollID = proxy.currentAnchor?.id as? String loadMoreSecondaryItems() } .onDisappear { if let id = lastScrollID { proxy.scrollTo(id, anchor: .bottom) } } } .background(Color.gray) } } }
方案3:移除第一个LazyVGrid的固定表头(业务允许时)
如果不需要固定表头,移除.pinnedViews(.sectionHeaders)参数可直接解决布局计算异常问题:
LazyVGrid(columns: [GridItem(alignment: .topLeading)], alignment: .leading, spacing: .zero) { // 移除pinnedViews参数 // 原有内容 }
内容的提问来源于stack exchange,提问作者Jaime Alcántara Arnela
相关产品推荐
相关产品推荐

