使用 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 修饰器,正是为了能够更简单的实现以上需求。
创建分页对齐效果(.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 子组件的顶部:

使用 limitBehavior 调整滑动速度
默认情况下,viewAligned 的 limitBehavior 参数使用 .automatic
。系统会自动调整滑动速度,在某些情况下可能会很慢。
例如,下面这个垂直滑动的速度非常慢,虽然可以对齐:
我们可以手动设置 limitBehavior 参数来调整速度:
.scrollTargetBehavior(.viewAligned(limitBehavior: .alwaysByOne))

使用示例
用 iOS 17 新的 SwiftUI API,scrollPosition(id:) 和 scrollTargetBehavior(.viewAligned) 控制滚动效果,再实现了一个自定义的 page control (中间的灰点点),新的启动页效果很满意。 pic.twitter.com/ckhd68USGP
— @jakehao (@haojianzong) January 21, 2024
Scroll and Pagination
— Polarizz (@polarizz39) June 14, 2024
Paging is used for granular control (zoomed in). Scrolling is used for fast scrubbing (zoomed out).
This can be done with just SwiftUI by toggling paging in ScrollView via .scrollTargetBehavior(). However, SwiftUI's ScrollView does not play well with… pic.twitter.com/BVmQOCjufh
Is the market share of iOS 17 substantial enough to consider adopting some new APIs like scrollTargetBehavior, which can replace the UIPageViewController I currently use? pic.twitter.com/jljRZj1uo8
— Weichao Deng (@JuniperPhoton) June 16, 2024