如何利用ViewThatFits优先显示首个添加视图?解决适配难题
问题:优先保留顶部项目的垂直自适应布局需求
有多个需要展示的项目,但可用垂直空间无法容纳全部,希望尽可能多显示且优先保留首个项目。使用ViewThatFits几乎能实现需求,但它默认优先显示最后添加的项目。我希望优先显示首个项目,将无法适配的后续项目从最后一项开始隐藏(如先隐藏Seven,再隐藏Six等),但当前代码的实际效果是Seven始终可见,One、Two等被依次隐藏。请问是否可通过ViewThatFits实现该需求,或有其他可行方案?
当前代码
struct ContentView: View { let items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"] var body: some View { VStack(spacing: 0) { ForEach(items, id: \.self) { RowView(title: $0) } Spacer() } .frame(width: 200, height: 150) .background(.fill) } } struct RowView: View { let title: String var body: some View { ViewThatFits { // 如果这个VStack无法适配可用空间,就会使用0高度视图替代 VStack(spacing: 0) { Text(title) .padding(.vertical, 5) Divider() } Color.clear .frame(height: 0) } } }
当前效果截图

解决方案
原方案失效原因
原代码中每个RowView单独使用ViewThatFits,但VStack的布局逻辑是从上到下分配空间:当整体空间不足时,底部的Spacer会向上挤压顶部的行,导致顶部行的ViewThatFits先触发切换到0高度视图,最终保留底部项目,与需求完全相反。
方案一:嵌套ViewThatFits实现固定数量项目适配
通过嵌套ViewThatFits,优先尝试显示全部项目,依次减少末尾项目直到找到适配的最大子集,逻辑简单直接:
struct ContentView: View { let items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"] var body: some View { ViewThatFits(in: .vertical) { FullListView(items: items) FullListView(items: Array(items.dropLast())) FullListView(items: Array(items.dropLast(2))) FullListView(items: Array(items.dropLast(3))) FullListView(items: Array(items.dropLast(4))) FullListView(items: Array(items.dropLast(5))) FullListView(items: [items.first!]) } .frame(width: 200, height: 150) .background(.fill) } } struct FullListView: View { let items: [String] var body: some View { VStack(spacing: 0) { ForEach(items, id: \.self) { title in VStack(spacing: 0) { Text(title) .padding(.vertical, 5) Divider() } } Spacer() } } }
方案二:动态计算可显示项目(适配任意数量项目)
如果项目数量较多,手动嵌套不现实,可以通过PreferenceKey收集行高,动态计算能容纳的顶部项目数量:
- 定义偏好键收集行高:
struct RowHeightPreferenceKey: PreferenceKey { static var defaultValue: [CGFloat] = [] static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) { value.append(contentsOf: nextValue()) } }
- 修改ContentView实现动态适配:
struct ContentView: View { let items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"] @State private var visibleCount = 0 var body: some View { GeometryReader { geo in VStack(spacing: 0) { ForEach(Array(items.prefix(visibleCount)), id: \.self) { title in RowView(title: title) .background( GeometryReader { rowGeo in Color.clear .preference(key: RowHeightPreferenceKey.self, value: [rowGeo.size.height]) } ) } Spacer() } .onPreferenceChange(RowHeightPreferenceKey.self) { heights in let totalHeight = heights.reduce(0, +) if totalHeight <= geo.size.height { if visibleCount < items.count { visibleCount += 1 } } else { if visibleCount > 1 { visibleCount -= 1 } } } .onAppear { visibleCount = items.count } } .frame(width: 200, height: 150) .background(.fill) } } struct RowView: View { let title: String var body: some View { VStack(spacing: 0) { Text(title) .padding(.vertical, 5) Divider() } } }
该方案会自动计算可容纳的最大顶部项目数,空间变化时也会动态调整。
内容的提问来源于stack exchange,提问作者Jordan H
相关产品推荐
相关产品推荐

