何曾几时,在 swiftui 开发中的秃头小码农们迫切需要一种能够读取当前滚动状态的方法。
在过去,他们往往需要借助于 uikit 的神秘力量。不过这一切在 swiftui 6.0 中已成“沧海桑田”。
在本篇博文中,您将学到如下内容:
相信学完本课后,小伙伴们在需要监听滚动视图滚动阶段的应用场景中定能得心应手、游刃有余!
那还等什么呢?让我们马上开始吧!let‘s go!!!😃
1. scrollview 滚动阶段简介
所谓滚动阶段(scroll phase)是指滚动视图在滚动前、滚动中以及滚动后所处的不同阶段。
早在 macos 10.9+ 的 coregraphics 中就有滚动阶段的概念了:
如果我们能及时的读取各个滚动阶段的值,我们就可以根据它们为滚动视图提供更加“银杏化”的定制和更流畅滚动附加体验。
在 swiftui 6.0 之前,我们无法使用行之有效的方法来读取滚动视图当前所处的滚动阶段,只有委身救助于 uikit 的秉轴持钧。
然而,这一切在 swiftui 6.0 中有了翻天覆地的变化!
2. 普度众生的 swiftui 6.0
自从 swiftui 6.0(ios 18.0)开始,“顿悟”的苹果终于提供滚动阶段的监听功能了。
一方面,我们有了描述滚动阶段的新类型 scrollphase:
它包含 5 个枚举值分别对应于 5 种滚动阶段:
@frozen public enum scrollphase : equatable {
case idle
case tracking
case interacting
case decelerating
case animating
public var isscrolling: bool { get }
}
这些滚动阶段的含义如下所示:
- idle - 表示当前滚动视图处于空闲状态,可以认为“嘛事没有”;
- tracking - 表示当前用户正轻触滚动视图但并没有开始滚动;
- interacting - 表示用户正在开始或继续滚动着视图的内容;
- decelerating -表示用户已结束滚动操作,滚动视图的滚动正在减速直至静止状态;
- animating - 表示滚动视图被 scrollposition 或 scrollviewreader 类型通过代码动态滚动到了指定的位置;
另一方面,我们有了新的视图改器方法 onscrollphasechange 专注于滚动阶段的监听:
有了以上两者的珠联璧合,现在我们在 swiftui 6.0 即可轻而易举的监听任何滚动视图的滚动阶段啦:
struct contentview: view {
var body: some view {
scrollview {
foreach(1...50, id: \.self) { i in
text("item \(i)")
.font(.title)
.padding()
divider()
}
}
.onscrollphasechange { old, new in
guard old != new else { return }
print("new phase: \(new)")
}
}
}
在 xcode 16beta 中运行效果如下所示:
scrollphase 类型还提供一个 isscrolling 计算属性,我们可以用它来判断当前是否正在滚动。比如,假若视图正在被滚动我们就“遮挡”它的显示内容:
struct contentview: view {
@state var isscrolling = false
var body: some view {
scrollview {
foreach(1...50, id: \.self) { i in
text("item \(i)")
.font(.title)
.padding()
divider()
}
.redacted(reason: isscrolling ? .placeholder : [])
}
.onscrollphasechange { old, new in
guard old != new else { return }
print("正在滚动?\(new.isscrolling)")
isscrolling = new.isscrolling
}
}
}
执行效果如下图所示:
3. 滚动阶段更改上下文(scrollphasechangecontext)
除此之外,swiftui 6.0 中新增的 onscrollphasechange 修改器还提供另一种重载(overloading)形式,在该重载方法的闭包中我们会得到一个 scrollphasechangecontext 上下文对象,使用它我们可以更多的掌控滚动的其它全局信息:
nonisolated
func onscrollphasechange(_ action: @escaping (scrollphase, scrollphase, scrollphasechangecontext) -> void) -> some view
演示代码如下所示,可以看到在其中我们使用 scrollphasechangecontext 上下文对象打印出了更多的与滚动相关的信息:
struct contentview: view {
var body: some view {
scrollview {
foreach(1...50, id: \.self) { i in
text("item \(i)")
.font(.title)
.padding()
divider()
}
}
.onscrollphasechange { old, new, context in
guard old != new else { return }
print("\(new)\n\(context)")
}
}
}
运行结果如下所示:
4. 如何监听列表(list)的滚动阶段
虽然 swiftui 6.0 破茧而出的“大杀器” onscrollphasechange 对于我们监听滚动状态大有裨益,不过目前它只能应用在 scrollview 视图的外层。这意味着,如果将其放在 list 上将会“徒劳无功”:
struct contentview: view {
var body: some view {
list {
foreach(1...50, id: \.self) { i in
text("item \(i)")
.font(.title)
.padding()
}
}
.onscrollphasechange { old, new, context in
guard old != new else { return }
print("\(new)\n\(context)")
}
}
}
上述代码附着在 list 之上的 onscrollphasechange 修改器回调闭包将会无所事事,直接沦为“不舞之鹤”。
诚然我们可以使用 scrollview 来平替 list,不过如果能在 list 上直接监听滚动阶段岂不更妙?
在 ios 18.0beta 中,我们可以通过将 list 包裹在 form 容器中暂时绕开此问题:
struct contentview: view {
var body: some view {
form {
list {
foreach(1...50, id: \.self) { i in
text("item \(i)")
.font(.title)
.padding()
}
}
}
.onscrollphasechange { old, new, context in
guard old != new else { return }
print("\(new)\n\(context)")
}
}
}
运行代码可以看到,我们用 onscrollphasechange 修改器成功的捕获到了 list 中滚动阶段的改变以及其它滚动信息:
我不确定这一情况在 ios 18.0 正式版中是否能够修复,让我们拭目以待吧!
总结
在本篇博文中,我们介绍了 swiftui 6.0(ios 18.0)滚动视图最新的滚动阶段(scroll phase)监听功能,并讨论了如何在原本不支持该功能的列表(list)上使用它。
感谢观赏,再会啦!😎
发表评论