onScrollGeometryChange 和 onGeometryChange 详解

0 阅读3分钟

SwiftUI 中 onScrollGeometryChange 和 onGeometryChange 详解

SwiftUI 在过去几年里,一直有一个痛点:

获取布局信息太麻烦

尤其是:

  • 获取 View 尺寸
  • 获取滚动 offset
  • 获取 frame
  • 获取可见区域

以前通常需要:

GeometryReader
+ PreferenceKey
+ coordinateSpace

一堆黑魔法。

而从新一代 SwiftUI 开始,Apple 正在逐渐用:

  • onGeometryChange
  • onScrollGeometryChange

替代传统方案。


为什么 Apple 要新增这些 API

以前:

GeometryReader { proxy in
    ...
}

有很多问题:

  • 会参与布局
  • 容易撑满空间
  • 很难控制
  • 性能不理想
  • PreferenceKey 写法复杂
  • ScrollView offset 获取非常痛苦

Apple 后来发现:

开发者真正想要的,
只是“观察几何变化”

而不是:

创建一个特殊布局容器

因此诞生了:

.onGeometryChange()

.onScrollGeometryChange()

一、onGeometryChange


它是什么

onGeometryChange 用来:

监听 View 自身几何信息变化。

例如:

  • size
  • frame
  • position
  • safeAreaInsets
  • bounds

等。

它可以理解成:

GeometryReader 的现代替代方案

基本用法

Text("Hello")
    .onGeometryChange(for: CGSize.self) { geometry in
        geometry.size
    } action: { newSize in
        print(newSize)
    }

API 结构解析


1. for

指定最终监听的数据类型。

例如:

CGSize.self
CGFloat.self
CGRect.self

2. transform

{ geometry in
    geometry.size
}

这里:

  • geometry 类似 GeometryProxy
  • 用来提取需要的数据

3. action

{ newValue in
}

当值发生变化时触发。


监听宽度变化

Color.red
    .frame(height: 100)
    .onGeometryChange(for: CGFloat.self) { geometry in
        geometry.size.width
    } action: { width in
        print(width)
    }

获取 frame

.onGeometryChange(for: CGRect.self) { geometry in
    geometry.frame(in: .global)
} action: { frame in
    print(frame)
}

这相当于以前复杂的:

GeometryReader + PreferenceKey

onGeometryChange 最大的意义

以前:

想观察布局
必须参与布局

现在:

终于可以:
观察布局
而不影响布局

这是非常大的变化。


二、onScrollGeometryChange

这是更高级的版本。

专门给 ScrollView 使用。


它是什么

用于监听:

ScrollView 的几何变化

例如:

  • contentOffset
  • contentSize
  • visibleRect
  • contentInsets
  • containerSize

等。


为什么它非常重要

以前 SwiftUI 获取滚动 offset:

极其痛苦。

通常需要:

GeometryReader
+ PreferenceKey
+ coordinateSpace
+ offset计算

一堆黑魔法。

现在:

.onScrollGeometryChange()

官方终于支持。


基本用法

ScrollView {
    ...
}
.onScrollGeometryChange(for: CGFloat.self) { geometry in
    geometry.contentOffset.y
} action: { oldValue, newValue in
    print(newValue)
}

ScrollGeometry 中有什么

这里的 geometry 不是普通 GeometryProxy。

而是:

ScrollGeometry

它包含:


contentOffset

geometry.contentOffset

滚动偏移量。

最常用。


contentSize

geometry.contentSize

内容总大小。


containerSize

geometry.containerSize

ScrollView 可视区域大小。


visibleRect

geometry.visibleRect

当前可见区域。


contentInsets

geometry.contentInsets

包括:

  • safeArea
  • safeAreaPadding
  • safeAreaInset

之后的最终 inset。


获取 Scroll Offset

@State private var scrollY: CGFloat = 0

ScrollView {
    ...
}
.onScrollGeometryChange(for: CGFloat.self) { geometry in
    geometry.contentOffset.y
} action: { _, y in
    scrollY = y
}

导航栏渐变

.opacity(min(scrollY / 100, 1))

这是很多 App Store 风格页面的核心。


检测是否滚到底

let isBottom =
geometry.visibleRect.maxY >= geometry.contentSize.height

视差滚动

以前很复杂。

现在只需要:

geometry.contentOffset

即可实现。


两者核心区别

onGeometryChangeonScrollGeometryChange
监听对象普通 ViewScrollView
geometry 类型GeometryProxy 风格ScrollGeometry
获取 offset不方便官方支持
获取 visibleRect
获取 contentSize
适合场景普通布局监听滚动监听

为什么 onScrollGeometryChange 更高级

因为:

ScrollView 并不是普通布局

它内部存在:

  • viewport
  • contentLayout
  • clipping
  • bouncing
  • insets

普通 GeometryReader 很难正确获取。

因此 Apple 单独设计了:

ScrollGeometry

iOS 版本

onGeometryChange

iOS 16+

onScrollGeometryChange

iOS 18+

属于最新 API。


现代 SwiftUI 的趋势

Apple 正在逐渐废弃:

GeometryReader 黑魔法时代

转向:

声明式几何观察

包括:

  • onGeometryChange
  • onScrollGeometryChange
  • scrollPosition
  • scrollTargetLayout
  • scrollTargetBehavior
  • scrollVisibility

这一整套新体系。


最后

现在可以这样理解:


GeometryReader

创建一个特殊布局容器

onGeometryChange

观察普通 View 的布局变化

onScrollGeometryChange

观察 ScrollView 的滚动几何变化

这也是 SwiftUI 未来布局系统的重要方向:

观察布局
而不是参与布局