SwiftUI数组遍历优化:首列固定的水平滚动表格实现问询
SwiftUI 首列固定、其余列水平滚动表格的优化实现
核心优化思路
避免原方案中每列单独遍历数据源的冗余操作,改为仅对数据源进行两次遍历:一次渲染固定首列,一次渲染滚动区域内的所有行内容,大幅减少不必要的数组遍历开销。
完整优化代码
1. 数据结构与数据源定义
struct SampleData: Identifiable { let id = UUID() let fixedColumn: String let column1: String let column2: String let column3: String // 可根据业务需求扩展更多列字段 } // 测试数据源 let datas: [SampleData] = [ SampleData(fixedColumn: "行1", column1: "数据1-1", column2: "数据1-2", column3: "数据1-3"), SampleData(fixedColumn: "行2", column1: "数据2-1", column2: "数据2-2", column3: "数据2-3"), SampleData(fixedColumn: "行3", column1: "数据3-1", column2: "数据3-2", column3: "数据3-3"), SampleData(fixedColumn: "行4", column1: "数据4-1", column2: "数据4-2", column3: "数据4-3"), ]
2. 优化后的表格视图
struct TablesView: View { let datas: [SampleData] // 统一管理滚动列的表头标题,便于维护 private let scrollableColumnTitles = ["列1", "列2", "列3"] var body: some View { HStack(spacing: 0) { // 固定首列(包含表头+内容) VStack(spacing: 0) { // 首列表头 Text("固定列") .frame(width: 120, height: 50) .background(Color.gray.opacity(0.2)) .border(Color.black) // 首列内容区域 ScrollView { VStack(spacing: 0) { ForEach(datas) { item in Text(item.fixedColumn) .frame(width: 120, height: 50) .border(Color.black) } } } } // 可水平滚动的列区域 ScrollView(.horizontal) { VStack(spacing: 0) { // 滚动列表头 HStack(spacing: 0) { ForEach(scrollableColumnTitles, id: \.self) { title in Text(title) .frame(width: 150, height: 50) .background(Color.gray.opacity(0.2)) .border(Color.black) } } // 滚动列内容:仅遍历一次数据源,每行渲染所有非首列数据 VStack(spacing: 0) { ForEach(datas) { item in HStack(spacing: 0) { Text(item.column1) .frame(width: 150, height: 50) .border(Color.black) Text(item.column2) .frame(width: 150, height: 50) .border(Color.black) Text(item.column3) .frame(width: 150, height: 50) .border(Color.black) // 新增列时,只需在此添加对应字段的Text即可 } } } } } } } }
进阶优化:同步垂直滚动位置
如果需要首列与滚动区域的垂直滚动保持同步,可以通过监听滚动偏移量实现:
struct TablesView: View { let datas: [SampleData] private let scrollableColumnTitles = ["列1", "列2", "列3"] // 绑定滚动偏移量,用于同步两边滚动 @State private var scrollOffset: CGFloat = 0 var body: some View { HStack(spacing: 0) { // 固定首列 VStack(spacing: 0) { Text("固定列") .frame(width: 120, height: 50) .background(Color.gray.opacity(0.2)) .border(Color.black) ScrollView { VStack(spacing: 0) { ForEach(datas) { item in Text(item.fixedColumn) .frame(width: 120, height: 50) .border(Color.black) } } // 监听首列的滚动偏移 .background( GeometryReader { proxy in Color.clear .preference(key: ScrollOffsetKey.self, value: proxy.frame(in: .named("scrollSync")).minY) } ) } .coordinateSpace(name: "scrollSync") .onPreferenceChange(ScrollOffsetKey.self) { newValue in scrollOffset = newValue } } // 可滚动区域 ScrollView(.horizontal) { ScrollView { VStack(spacing: 0) { // 滚动列表头 HStack(spacing: 0) { ForEach(scrollableColumnTitles, id: \.self) { title in Text(title) .frame(width: 150, height: 50) .background(Color.gray.opacity(0.2)) .border(Color.black) } } // 滚动列内容 VStack(spacing: 0) { ForEach(datas) { item in HStack(spacing: 0) { Text(item.column1) .frame(width: 150, height: 50) .border(Color.black) Text(item.column2) .frame(width: 150, height: 50) .border(Color.black) Text(item.column3) .frame(width: 150, height: 50) .border(Color.black) } } } } } // 同步首列的滚动偏移 .offset(y: scrollOffset) .coordinateSpace(name: "scrollSync") } } } } // 自定义PreferenceKey,用于传递滚动偏移量 private struct ScrollOffsetKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} }
优化效果说明
- 原方案每新增一列就多一次数组遍历,优化后无论多少列,仅需两次遍历
- 滚动列的表头与内容统一管理,新增/删除列的操作更直观
- 同步滚动的进阶方案提升了用户操作的连贯性
内容的提问来源于stack exchange,提问作者Colton
相关产品推荐
相关产品推荐

