SwiftUI:如何让ScrollView内的List显示所有行无需自身滚动
在ScrollView中嵌入不滚动的List并自适应高度的解决方案
问题描述
我正在开发的App有一个详情视图,使用ScrollView展示大量信息,希望在该ScrollView内嵌入List来展示特定数据。但List嵌套在ScrollView中时必须添加frame修饰符,我希望List能显示所有行且自身不滚动,由外层ScrollView负责滚动,这就需要准确计算List的高度以容纳所有行。
若给List设置固定高度(如200),当行数过多时List自身会出现滚动,这不符合需求。我曾尝试通过环境变量defaultMinListRowHeight计算高度,但仅当行高不超过默认值时有效,而当前场景中行高很可能超过默认值。
附上我基于defaultMinListRowHeight编写的List尺寸适配代码:
struct DynamicList<SelectionValue: Identifiable, Content: View>: View { @Environment(\.defaultMinListRowHeight) private var minRowHeight var values: [SelectionValue] @ViewBuilder var content: (SelectionValue) -> Content var body: some View { List(values) { value in content(value) .lineLimit(1) } .listStyle(.plain) .scrollDisabled(true) .frame(minHeight: minRowHeight * CGFloat(values.count)) } }
解决方案
方法一:动态测量每一行高度并计算总高度
通过GeometryReader和PreferenceKey捕获每一行的实际高度,累加后设置List的frame高度,适配任意行高:
1. 实现高度测量辅助组件
struct HeightMeasurableView<Content: View>: View { let content: Content let onHeightChange: (CGFloat) -> Void var body: some View { content .background( GeometryReader { proxy in Color.clear .preference(key: HeightPreferenceKey.self, value: proxy.size.height) } ) .onPreferenceChange(HeightPreferenceKey.self) { height in onHeightChange(height) } } } private struct HeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {} }
2. 改造DynamicList组件
struct DynamicList<SelectionValue: Identifiable, Content: View>: View { var values: [SelectionValue] @ViewBuilder var content: (SelectionValue) -> Content @State private var rowHeights: [CGFloat] = [] @Environment(\.defaultMinListRowHeight) private var minRowHeight var body: some View { List(values.indices, id: \.self) { index in HeightMeasurableView(content: content(values[index])) { height in if rowHeights.count > index { rowHeights[index] = height } else { rowHeights.append(height) } } } .listStyle(.plain) .scrollDisabled(true) // 累加行高,加上plain List默认的上下内边距(16为上下8的总和) .frame(height: rowHeights.reduce(0, +) + 16) .onAppear { rowHeights = Array(repeating: minRowHeight, count: values.count) } .onChange(of: values) { _ in // 数据源更新时重置行高数组 rowHeights = Array(repeating: minRowHeight, count: values.count) } } }
注意事项
- 若使用非plain风格的List,需调整frame中的额外内边距数值,不同listStyle的内边距规则不同。
- 当数据量过大(如数百行),这种测量方式可能带来性能损耗,需根据实际场景评估。
方法二:用VStack/LazyVStack替代List
如果不需要List的滑动删除、多选等特性,直接用VStack模拟List样式是更轻量的方案,无需计算高度:
struct DynamicStack<SelectionValue: Identifiable, Content: View>: View { var values: [SelectionValue] @ViewBuilder var content: (SelectionValue) -> Content var body: some View { VStack(spacing: 0) { ForEach(values) { value in content(value) .padding(.horizontal) .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, 10) .border(.gray.opacity(0.2), edge: .bottom) } } } }
内容的提问来源于stack exchange,提问作者Jordan Herget
相关产品推荐
相关产品推荐

