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

SwiftUI垂直分页TabView内嵌ScrollView:如何获取VStack正确尺寸

垂直分页TabView内嵌ScrollView的自适应尺寸解决方案

问题根源

你当前通过旋转TabView和内部视图模拟垂直分页,但旋转会扭曲坐标系,导致GeometryReader无法准确测量视图的原始尺寸,进而引发布局崩溃或固定尺寸无法适配内容的问题。

解决方案:先测量后旋转

我们需要在视图未旋转的状态下捕获其自然尺寸,再应用旋转并根据测量结果调整布局。具体步骤如下:

  1. 定义尺寸捕获偏好键
    用PreferenceKey传递视图的原始尺寸:
struct ViewSizeKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}
  1. 修改主体视图实现自适应
    捕获每个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 08:59:21