TL;DR: 在 SwiftUI 中,使用
GeometryReader、onGeometryChange、visualEffect或containerRelativeFrame,可动态获取视图尺寸以实现响应式布局。
背景
在 SwiftUI 中,动态获取视图的尺寸是一个常见需求,尤其是在需要响应尺寸变化或调整布局的场景中。本文将介绍几种获取视图尺寸的常用方法及其适用场景。
使用 GeometryReader
GeometryReader 是适配性最强的方式,适用于所有版本的 SwiftUI。通常结合 overlay 或 background 使用,以避免影响主视图的尺寸。
示例代码
blueRectangle
  .background(
    GeometryReader { proxy in
      Color.clear // 创建与主视图尺寸一致的透明视图
        .task(id: proxy.size) {
          size = proxy.size // 获取尺寸并监听变化
        }
    }
  )特点
- 将获取尺寸的逻辑封装在 background,不影响主视图布局。
- 使用 task(id:)确保视图加载时立即获取尺寸,并在proxy.size变化时更新。
使用 onGeometryChange
从 iOS 16 开始,onGeometryChange 提供了更简洁的方式来监听尺寸变化。
示例代码
struct SizeDemo: View {
    @State var size: CGSize?
    var body: some View {
        Rectangle()
            .onGeometryChange(for: CGSize.self) { proxy in
                proxy.size
            } action: {
                size = $0
            }
    }
}iOS 18+ 新特性
支持同时获取新旧尺寸值,方便处理尺寸变化逻辑。
.onGeometryChange(for: CGSize.self) { proxy in
    proxy.size
} action: { old, new in
    size = new // 获取新尺寸
    print("Old size: \(old), New size: \(new)")
}使用 visualEffect
如果目标是根据视图尺寸应用视觉效果(如 offset、scaleEffect 等),visualEffect (iOS 17+) 提供了更直接的方式。
示例代码
struct SizeDemo: View {
    var body: some View {
        Rectangle()
            .foregroundStyle(.red)
            .visualEffect { effect, proxy in
                effect
                    .offset(y: proxy.size.height / 3) // 视图向下偏移自身高度的 1/3
            }
    }
}效果
 
                  
使用 containerRelativeFrame
containerRelativeFrame (iOS 17+) 是 GeometryReader 与 frame 的结合体,用于获取当前视图所在的特定容器(如窗口、NavigationStack、ScrollView)的尺寸,并将该尺寸作为自身的约束。
示例代码
以下代码将矩形设置为窗口宽度的 1/2 和高度的 1/4:
struct TransformsDemo: View {
    var body: some View {
        Rectangle()
            .containerRelativeFrame([.horizontal, .vertical]) { length, axis in
                if axis == .vertical {
                    return length / 4
                } else {
                    return length / 2
                }
            }
    }
}- containerRelativeFrame将沿- Rectangle向上寻找第一个满足条件的容器,当前为窗口
效果
 
                  
容器适应性
containerRelativeFrame 会自动选择合适的容器。例如,当矩形放置在 NavigationStack 中时,计算的尺寸基于 NavigationStack 的尺寸。
struct TransformsDemo: View {
    var body: some View {
        NavigationStack {
            Rectangle()
                .containerRelativeFrame([.horizontal, .vertical]) { length, axis in
                    if axis == .vertical {
                        return length / 4
                    } else {
                        return length / 2
                    }
                }
        }
        .frame(width: 300, height: 300) // 指定 NavigationStack 的尺寸
        .border(.red, width: 4)
    }
}效果
 
                  
ScrollView 中的应用
containerRelativeFrame 还能用来动态调整滚动视图中的子视图尺寸。例如,将子视图宽度设置为滚动视图宽度的 1/3:
struct ScrollViewDemo: View {
    var body: some View {
        ScrollView(.horizontal) {
            HStack(spacing: 10) {
                ForEach(0..<10) { _ in
                    Rectangle()
                        .fill(.purple)
                        .aspectRatio(3 / 2, contentMode: .fit)
                        .containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 0)
                }
            }
        }
    }
}效果
 
                  
方法对比
| 方法 | 适用场景 | 特点 | 
|---|---|---|
| GeometryReader | 适用于所有 SwiftUI 版本 | 灵活性强,但需要开发者构建更多的代码 | 
| onGeometryChange | iOS 16+ 简化监听尺寸变化 | 语义清晰,支持监听新旧尺寸(iOS 18+) | 
| visualEffect | iOS 17+ 用于动态调整渲染效果 | 简洁高效,直接使用视图的 GeometryProxy信息 | 
| containerRelativeFrame | iOS 17+ 适用于相对容器计算的场景,如窗口、滚动视图等 | 自动匹配容器上下文,便于对子视图进行动态尺寸调整 | 
延伸阅读
"加入我们的 Discord 社区,与超过 2000 名苹果生态的中文开发者一起交流!"