TL;DR: 在 SwiftUI 中,使用
GeometryReader
、onGeometryChange
、visualEffect
或containerRelativeFrame
,可动态获取视图尺寸以实现响应式布局。
背景
在 SwiftUI 中,动态获取视图的尺寸是一个常见需求,尤其是在需要响应尺寸变化或调整布局的场景中。本文将介绍几种获取视图尺寸的常用方法及其适用场景。
使用 GeometryReader
GeometryReader
是适配性最强的方式,适用于所有版本的 SwiftUI。通常结合 overlay
或 background
使用,以避免影响主视图的尺寸。
示例代码
Swift
blueRectangle
.background(
GeometryReader { proxy in
Color.clear // 创建与主视图尺寸一致的透明视图
.task(id: proxy.size) {
size = proxy.size // 获取尺寸并监听变化
}
}
)
特点
- 将获取尺寸的逻辑封装在
background
,不影响主视图布局。 - 使用
task(id:)
确保视图加载时立即获取尺寸,并在proxy.size
变化时更新。
使用 onGeometryChange
从 iOS 16 开始,onGeometryChange
提供了更简洁的方式来监听尺寸变化。
示例代码
Swift
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+ 新特性
支持同时获取新旧尺寸值,方便处理尺寸变化逻辑。
Swift
.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+) 提供了更直接的方式。
示例代码
Swift
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:
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
}
}
}
}
containerRelativeFrame
将沿Rectangle
向上寻找第一个满足条件的容器,当前为窗口
效果
容器适应性
containerRelativeFrame
会自动选择合适的容器。例如,当矩形放置在 NavigationStack
中时,计算的尺寸基于 NavigationStack
的尺寸。
Swift
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:
Swift
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+ 适用于相对容器计算的场景,如窗口、滚动视图等 | 自动匹配容器上下文,便于对子视图进行动态尺寸调整 |