SwiftUI垂直分页TabView内嵌ScrollView:如何获取VStack正确尺寸
垂直分页TabView内嵌ScrollView的自适应尺寸解决方案
问题根源
你当前通过旋转TabView和内部视图模拟垂直分页,但旋转会扭曲坐标系,导致GeometryReader无法准确测量视图的原始尺寸,进而引发布局崩溃或固定尺寸无法适配内容的问题。
解决方案:先测量后旋转
我们需要在视图未旋转的状态下捕获其自然尺寸,再应用旋转并根据测量结果调整布局。具体步骤如下:
- 定义尺寸捕获偏好键
用PreferenceKey传递视图的原始尺寸:
struct ViewSizeKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() } }
- 修改主体视图实现自适应
捕获每个VStack的原始尺寸,再根据尺寸动态设置旋转后的框架:
@State private var itemSizes: [Int: CGSize] = [:] let colors: [Color] = [.red, .blue, .green] // 替换为你的颜色数组 var body: some View { GeometryReader { screenProxy in TabView { ForEach(colors, id: \.self) { color in ScrollView(.horizontal, showsIndicators: false) { HStack(alignment: .bottom, spacing: 20) { ForEach(0..<15) { i in VStack(alignment: .leading) { Text("Block test test test test test test \(i)") } // 在原始坐标系中测量VStack的真实尺寸 .background( GeometryReader { proxy in Color.clear .preference(key: ViewSizeKey.self, value: proxy.size) } ) .onPreferenceChange(ViewSizeKey.self) { size in itemSizes[i] = size } .rotationEffect(.degrees(-90)) // 旋转后宽高互换,用测量的原始尺寸设置框架 .frame( width: itemSizes[i]?.height ?? 70, height: itemSizes[i]?.width ?? screenProxy.size.width, alignment: .leading ) .background(Color.green) .id(i) } } .frame(height: screenProxy.size.height) .background(Color.blue) } .frame(width: screenProxy.size.height, height: screenProxy.size.width) .background(color) } } .frame(width: screenProxy.size.height, height: screenProxy.size.width) .rotationEffect(.degrees(90), anchor: .topLeading) .offset(x: screenProxy.size.width) .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) } }
关键改进说明
- 避免旋转干扰测量:在
VStack的背景中添加GeometryReader,确保在未旋转的坐标系中捕获真实尺寸。 - 动态适配框架:旋转后视图的宽高会互换,因此用原始尺寸的高度作为旋转后的宽度,原始宽度作为旋转后的高度。
- 移除固定尺寸:替换硬编码的
width:70,让视图根据内容自动调整大小。
补充:滚动到底部切换Tab
要实现滚动至底部切换Tab,可监听ScrollView的滚动位置:
@State private var currentTabIndex = 0 // 修改TabView绑定当前索引 TabView(selection: $currentTabIndex) { ForEach(colors.indices, id: \.self) { index in ScrollView(.horizontal, showsIndicators: false) { // ... 原有ScrollView内容 ... } .onScrollPositionChange { offsets in guard let offset = offsets.first else { return } // 计算内容总宽度(含间距) let totalContentWidth = itemSizes.values.map { $0.height + 20 }.reduce(0, +) // 判断是否滚动至底部 if offset >= totalContentWidth - screenProxy.size.height { // 切换到下一个Tab(处理边界) if currentTabIndex < colors.count - 1 { currentTabIndex += 1 } } } .tag(index) // ... 原有frame和背景设置 ... } }
内容的提问来源于stack exchange,提问作者Stefano Vet
相关产品推荐
相关产品推荐

