前言
本文探讨了如何使用 onScrollGeometryChange
视图修饰符有效地监控和管理滚动位置和几何。通过详细的代码示例和解释,你将学习如何利用这些工具创建动态和响应迅速的用户界面。
SwiftUI 是一个强大的框架,它简化了在苹果平台上构建用户界面的过程。SwiftUI 中的一个基本组件是 ScrollView,它允许用户通过滚动导航内容。然而,管理滚动位置和理解滚动交互可能是一个挑战。ScrollGeometry 和 onScrollGeometryChange 视图修饰符的引入解决了这些挑战,为开发者提供了更多的控制和对滚动行为的深入了解。
什么是 ScrollPosition
ScrollPosition 是一种类型,允许开发者以编程方式读取或更改滚动位置。虽然有用,但当用户使用手势与滚动视图交互时,它显得不够全面。以下是一个展示 ScrollPosition 使用的示例:
代码语言:swift复制struct ContentView: View {
@State private var position = ScrollPosition(edge: .top)
var body: some View {
ScrollView {
Button("Scroll to offset") {
position.scrollTo(point: CGPoint(x: 0, y: 100))
}
ForEach(1..<100) { index in
Text(verbatim: index.formatted())
.id(index)
}
}
.scrollPosition($position)
.animation(.default, value: position)
}
}
在这个示例中,我们将滚动视图绑定到一个状态属性。当按下按钮时,滚动视图会将其内容偏移移动到指定点。然而,我们无法读取用户通过手势交互设置的具体内容偏移。
引入 ScrollGeometry
SwiftUI 的新 ScrollGeometry 类型以及 onScrollGeometryChange 视图修饰符提供了一个解决方案。这些工具允许开发者在用户交互期间准确读取内容偏移。
使用 onScrollGeometryChange
让我们探索如何使用 onScrollGeometryChange 视图修饰符与 ScrollGeometry:
代码语言:swift复制struct ContentView: View {
@State private var scrollPosition = ScrollPosition(y: 0)
@State private var offsetY: CGFloat = 0
var body: some View {
ScrollView {
ForEach(1..<100, id: .self) { number in
Text(verbatim: number.formatted())
.id(number)
}
}
.scrollPosition($scrollPosition)
.onScrollGeometryChange(for: CGFloat.self) { geometry in
geometry.contentOffset.y
} action: { oldValue, newValue in
if oldValue != newValue {
offsetY = newValue
}
}
.onChange(of: offsetY) {
print(offsetY)
}
}
}
onScrollGeometryChange 视图修饰符接受三个参数:
- 类型参数:指定要跟踪的滚动几何类型。在此示例中,我们使用
CGFloat
来跟踪内容偏移的 Y 轴。 - 转换闭包:从 ScrollGeometry 实例中提取所需信息。
- 动作闭包:处理滚动几何的变化,通过比较旧值和新值,允许我们相应地更新状态属性。
高级滚动几何跟踪
ScrollGeometry 提供了许多有价值的属性,如内容偏移、边界、容器大小、可见矩形、内容插入和内容大小。开发者可以提取单个属性或组合多个属性以获得全面的见解。
以下是一个结合内容大小和可见矩形跟踪的示例:
代码语言:swift复制struct ContentView: View {
struct ScrollData: Equatable {
let size: CGSize
let visible: CGRect
}
@State private var scrollPosition = ScrollPosition(y: 0)
@State private var scrollData = ScrollData(size: .zero, visible: .zero)
var body: some View {
ScrollView {
ForEach(1..<100, id: .self) { number in
Text(verbatim: number.formatted())
.id(number)
}
}
.scrollPosition($scrollPosition)
.onScrollGeometryChange(for: ScrollData.self) { geometry in
ScrollData(size: geometry.contentSize, visible: geometry.visibleRect)
} action: { oldValue, newValue in
if oldValue != newValue {
scrollData = newValue
}
}
.onChange(of: scrollData) {
print(scrollData)
}
}
}
在这个示例中,我们定义了一个 ScrollData
结构来保存大小和可见矩形属性。在使用 onScrollGeometryChange 视图修饰符时,我们将 ScrollData
作为转换闭包的返回类型,从 ScrollGeometry 实例中提取所有所需的数据。
完整代码示例分析
下面是一个完整的 SwiftUI Demo,其中包含了我们刚刚讨论的 ScrollView、ScrollGeometry 和 onScrollGeometryChange 的使用示例。你可以在 Xcode 中运行这个项目来观察其效果。
完整代码示例
代码语言:swift复制import SwiftUI
struct ContentView: View {
@State private var scrollPosition = ScrollPosition(y: 0)
@State private var offsetY: CGFloat = 0
var body: some View {
VStack {
Text("Scroll Offset: (offsetY, specifier: "%.2f")")
.padding()
ScrollView {
ForEach(1..<100, id: .self) { number in
Text(verbatim: number.formatted())
.padding()
.frame(maxWidth: .infinity)
.background(Color(.secondarySystemBackground))
.cornerRadius(8)
.padding(.horizontal)
.id(number)
}
}
.scrollPosition($scrollPosition)
.onScrollGeometryChange(for: CGFloat.self) { geometry in
geometry.contentOffset.y
} action: { oldValue, newValue in
if oldValue != newValue {
offsetY = newValue
}
}
.onChange(of: offsetY) {
print(offsetY)
}
}
}
}
struct ScrollData: Equatable {
let size: CGSize
let visible: CGRect
}
struct AdvancedContentView: View {
@State private var scrollPosition = ScrollPosition(y: 0)
@State private var scrollData = ScrollData(size: .zero, visible: .zero)
var body: some View {
VStack {
Text("Content Size: (scrollData.size.width, specifier: "%.2f") x (scrollData.size.height, specifier: "%.2f")")
.padding()
Text("Visible Rect: (scrollData.visible.origin.x, specifier: "%.2f"), (scrollData.visible.origin.y, specifier: "%.2f") - (scrollData.visible.width, specifier: "%.2f") x (scrollData.visible.height, specifier: "%.2f")")
.padding()
ScrollView {
ForEach(1..<100, id: .self) { number in
Text(verbatim: number.formatted())
.padding()
.frame(maxWidth: .infinity)
.background(Color(.secondarySystemBackground))
.cornerRadius(8)
.padding(.horizontal)
.id(number)
}
}
.scrollPosition($scrollPosition)
.onScrollGeometryChange(for: ScrollData.self) { geometry in
ScrollData(size: geometry.contentSize, visible: geometry.visibleRect)
} action: { oldValue, newValue in
if oldValue != newValue {
scrollData = newValue
}
}
.onChange(of: scrollData) {
print(scrollData)
}
}
}
}
@main
struct ScrollViewDemoApp: App {
var body: some Scene {
WindowGroup {
TabView {
ContentView()
.tabItem {
Label("Basic", systemImage: "1.square.fill")
}
AdvancedContentView()
.tabItem {
Label("Advanced", systemImage: "2.square.fill")
}
}
}
}
}
如何运行
- 打开 Xcode 并创建一个新的 SwiftUI 项目。
- 将默认生成的
ContentView.swift
文件替换为上面的完整代码。 - 在
@main
注释下的应用程序入口点中,确保你的主视图是ScrollViewDemoApp
。 - 运行项目。
功能解释
ContentView
: 展示基本的滚动偏移追踪功能,通过onScrollGeometryChange
视图修饰符追踪 Y 轴的内容偏移。AdvancedContentView
: 展示更高级的滚动几何追踪功能,追踪内容大小和可见矩形的变化。ScrollViewDemoApp
: 包含TabView
,方便在基本和高级示例之间切换。
总结
今天,我们探讨了 SwiftUI 中的新 ScrollGeometry 类型和 onScrollGeometryChange 视图修饰符。这些工具为开发者提供了对滚动位置和交互的精确控制和洞察,增强了动态和响应迅速的用户界面的开发。通过利用这些功能,你可以创建更具吸引力和直观的应用程序。