如何实现SwiftUI ScrollView自适应内容大小并支持设置最大高度
实现方案
要实现「内容高度小于阈值时自适应收缩、超过阈值时固定最大高度可滚动」的ScrollView,核心是动态获取ScrollView内部内容的实际高度,再将ScrollView的高度限定为内容高度和最大阈值的较小值。
第一步:定义高度读取用的PreferenceKey
我们需要用SwiftUI的PreferenceKey机制读取ScrollView内部列表的实际高度:
private struct HeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() } }
第二步:修改ContentView代码
完整修改后的代码如下:
struct ContentView: View { @State var List1: [String] = [] @State var List2: [String] = [] // 存储两个列表的实际内容高度 @State private var list1ContentHeight: CGFloat = 0 @State private var list2ContentHeight: CGFloat = 0 // 统一设置两个列表的最大高度,可自行调整 private let maxListHeight: CGFloat = 40 var body: some View { VStack { Button("1-5") { List1 = ["1"] List2 = ["a", "b", "c", "d", "e"] } Button("3-3") { List1 = ["1", "2", "3"] List2 = ["a", "b", "c"] } Button("5-1") { List1 = ["1", "2", "3", "4", "5"] List2 = ["a"] } // 第一个列表的ScrollView ScrollView { VStack { ForEach(List1.indices, id: \.self) { idx in Text(List1[idx]) } } // 读取列表实际高度 .background(GeometryReader { geo in Color.clear .preference(key: HeightPreferenceKey.self, value: geo.size.height) }) // 同步高度到状态变量 .onPreferenceChange(HeightPreferenceKey.self) { height in list1ContentHeight = height } } // 高度取内容高度和最大高度的较小值 .frame(height: min(list1ContentHeight, maxListHeight)) Text("middle") // 第二个列表的ScrollView,逻辑和第一个完全一致 ScrollView { VStack { ForEach(List2.indices, id: \.self) { idx in Text(List2[idx]) } } .background(GeometryReader { geo in Color.clear .preference(key: HeightPreferenceKey.self, value: geo.size.height) }) .onPreferenceChange(HeightPreferenceKey.self) { height in list2ContentHeight = height } } .frame(height: min(list2ContentHeight, maxListHeight)) Spacer() Text("last") } } }
效果说明
- 列表项总高度小于
maxListHeight时,ScrollView高度跟随内容自动收缩,不会占用多余空间 - 列表项总高度超过
maxListHeight时,ScrollView固定为maxListHeight,内部内容可正常滚动 - 两个列表独立适配,互不影响
内容的提问来源于stack exchange,提问作者Chris Quenelle
相关产品推荐
相关产品推荐

