使用 scrollTargetBehavior 自定义 ScrollView 对齐效果
了解如何使用 iOS17 上新引入的 scrollTargetBehavior 修饰器,为你的滚动视图添加更多效果。
SwiftUI 从 iOS13 开始提供了 ScrollView 组件,用于快速创建支持滑动的视图效果。默认情况下,ScrollView 基于滑动手势的速度,来计算滑动停止的位置——遵循苹果的非线性动画效果,因此动画非常自然。
从 iOS17 开始,SwiftUI 引入了新的 scrollTargetBehavior
修饰符,允许我们自定义 ScrollView 的滚动行为,我们可以用它创建更高级的滑动效果。
你可以在这里找到完整的实例代码:
ScrollView 默认无限制滚动效果
我们创建如下实示例代码,创建苹果 App Store 的卡片式 UI 布局效果:
private var featuredSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("今日推荐")
.font(.title)
.fontWeight(.bold)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(apps) { app in
FeaturedAppCard(app: app)
}
}
}
}
}
默认情况下,ScrollView 组件会基于减速率和滚动手势的状态来计算滚动手势的结束位置。滚动可能在任意位置停止。
但在苹果的 App Store 中,每次滑动会停留在固定的位置,这种对其方式会使得滚动更加的自然:
在此之前,为了实现上面这样的效果,你可能需要深入定制 UIScrollView
,编写大量代码来监听滚动行为。iOS17 中引入的 .ScrollTargetBehavior 修饰器,正是为了能够更简单的实现以上需求。
使用 scrollTargetBehavior 修饰器
创建分页效果(.paging)
你只需要将 scrollTargetBehavior
修饰符,添加到 ScrollView 上即可轻松使用它:
private var featuredSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("今日推荐")
.font(.title)
.fontWeight(.bold)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(apps) { app in
FeaturedAppCard(app: app)
}
}
}
.scrollTargetBehavior(.paging)
}
}
使用 .paging
模式,用户在滚动时,会自动对齐到最近的页面,形成分页浏览的体验。常用于一页一页浏览内容。
但你可能已经发现了一个问题:随着卡片滑动,后续的卡片前半部分可能会被屏幕遮挡。造成这个问题的原因是:分页模式下,默认每次滑动的距离为一张卡片的宽度,因此无法确保每张卡片的头部位置固定。
要解决这个问题,我们需要使用 .viewAligned
模式。
创建视图对齐效果(.viewAligned)
.scrollTargetBehavior(.viewAligned)
修饰器需要搭配 .scrollTargetLayout()
修饰器一起使用。
可以使用 scrollTargetLayout()
修改器配置哪些视图应用于对齐。将此修改器应用于布局容器(如 LazyVStack 或 HStack),SwiftUI 会自动创建基于视图的对齐滚动效果。
看起来有点难懂,我们来看看实际的效果:
private var featuredSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("今日推荐")
.font(.title)
.fontWeight(.bold)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(apps) { app in
FeaturedAppCard(app: app)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
}
}
由于我们将 .scrollTargetLayout()
设置在 HStack 上,因此ScrollView 将自动对齐每个 HStack 子组件的顶部: