🔋

SwiftUI 布局指南:4种方式动态获取视图尺寸

(更新于 )

核心摘要:在 SwiftUI 早期,GeometryReader 是获取视图尺寸的唯一选择,但它常导致布局副作用。随着 SwiftUI 的演进(尤其是 iOS 16/17/18 的更新),我们有了 onGeometryChangevisualEffect 等性能更好、副作用更小的替代方案。

在 SwiftUI 中,动态获取视图的尺寸是一个常见需求。本文将梳理截至 2025 年主流的 4 种获取尺寸的方法,帮助你根据场景选择最佳方案。

1. 通用方案:GeometryReader

GeometryReader 是适配性最强的方式,适用于所有版本的 SwiftUI。为了避免它抢占空间破坏布局,最佳实践是将其结合 backgroundoverlay 使用。

示例代码

Swift
blueRectangle
  .background(
    GeometryReader { proxy in
      Color.clear // 创建与主视图尺寸一致的透明视图
        .task(id: proxy.size) {
          size = proxy.size // 获取尺寸并监听变化
        }
    }
  )

特点

  • 优点:兼容性最好,支持所有 iOS 版本。
  • 缺点:语法啰嗦,如果直接包裹视图会导致视图充满父容器。

2. 现代标准:onGeometryChange (推荐)

从 iOS 16 开始引入,并在 iOS 18 得到增强。这是目前获取视图尺寸最推荐的方式,它纯粹用于监听,不会像 GeometryReader 那样影响布局结构。

示例代码

Swift
struct SizeDemo: View {
    @State var size: CGSize?
    var body: some View {
        Rectangle()
            // iOS 16+ 基础用法
            .onGeometryChange(for: CGSize.self) { proxy in
                proxy.size
            } action: { newValue in
                size = newValue
            }
    }
}

iOS 18+ 增强特性

iOS 18 增加了对旧值的支持,方便处理动画或过渡逻辑:

Swift
.onGeometryChange(for: CGSize.self) { proxy in
    proxy.size
} action: { old, new in
    print("Size changed from \(old) to \(new)")
}

3. 视觉特效专用:visualEffect

如果你的目的是根据尺寸调整视觉效果(如 offsetscaleblur),而不是为了改变布局流,那么 iOS 17 引入的 visualEffect 是性能最高的选择。它不需要通过 @State 触发布局刷新。

示例代码

Swift
struct EffectDemo: View {
    var body: some View {
        Rectangle()
            .foregroundStyle(.red)
            .visualEffect { content, proxy in
                // 直接使用 proxy 获取尺寸,无需 State 绑定
                content.offset(y: proxy.size.height / 3)
            }
    }
}

效果

visualeffect-swiftui-demo

4. 容器相对布局:containerRelativeFrame

containerRelativeFrame (iOS 17+) 是处理“网格类”或“分屏类”布局的神器。它不直接告诉你尺寸数值,而是让你声明“我想要占容器宽度的几分之几”。

示例代码

以下代码将矩形设置为父容器(如 Window 或 ScrollView)宽度的 1/2 和高度的 1/4:

Swift
struct TransformsDemo: View {
    var body: some View {
        Rectangle()
            .containerRelativeFrame([.horizontal, .vertical]) { length, axis in
                if axis == .vertical {
                    return length / 4
                } else {
                    return length / 2
                }
            }
    }
}

效果与应用

它会自动向上查找最近的特定容器(如 NavigationStackScrollView)。

containerRelativeFrame-in-navigationStack

ScrollView 中实现“一屏显示三个卡片”的效果非常简单:

Swift
Rectangle()
    .containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 10)

containerRelativeFrame-scrollView

总结:如何选择?

方法最低版本适用场景推荐指数
onGeometryChangeiOS 16+首选。需要获取尺寸数值更新 State 时。⭐⭐⭐⭐⭐
visualEffectiOS 17+仅需调整视觉效果(偏移、缩放),无需触发布局更新。⭐⭐⭐⭐
containerRelativeFrameiOS 17+需基于父容器(屏幕/滚动视图)按比例布局时。⭐⭐⭐⭐
GeometryReaderiOS 13+维护旧项目,或需要获取坐标空间(CoordinateSpace)信息时。⭐⭐

延伸阅读

相关提示

订阅 Fatbobman 周报

每周精选 Swift 与 SwiftUI 开发技巧,加入众多开发者的行列。

立即订阅