You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 21:21:01