SwiftUI 6.0(iOS 18)ScrollView 全新的滚动位置(ScrollPosition)揭秘
概览
在只有方寸之间大小的手持设备上要想体面的向用户展示海量信息,滚动视图(ScrollView)无疑是绝佳的“东牀之选”。
在 SwiftUI 历史的长河中,总觉得苹果对于 ScrollView 视图功能的升级是在“挤牙膏”。这不,在本届最新 WWDC24 重磅打造的 SwiftUI 6.0 中就让我们来看看 ScrollView 又能挤出怎样的新花样吧?
在本篇博文中,您将学到如下精彩的内容:
- 概览
- 1. SwiftUI 6.0 之前的滚动世界
- 2. SwiftUI 6.0(iOS 18)中全新的 ScrollPosition 类型
- 3. “新老搭配,干活不累”
- 4. 如何判断当前滚动是由用户指尖触发的?
- 5. 实时监听滚动视图的内容偏移(ContentOffset)
- 总结
在 WWDC24 里,苹果对 SwiftUI 6.0 中滚动视图的全新升级无疑解了一众秃头码农们的额燃眉之急。
那还等什么呢?让我们马上开始滚动大冒险吧!
Let‘s rolling!!!😉
1. SwiftUI 6.0 之前的滚动世界
苹果从 SwiftUI 2.0 开始陆续“发力”向 ScrollView 增加了许多新特性,其中包括秃头码农们翘首跂踵的滚动位置读取与设置、滚动模式等高级功能。
在 SwiftUI 6.0 之前,我们是通过单一状态来读取和设置滚动位置的:
struct ContentView: View { @State private var position: Int? var body: some View { ScrollView { LazyVStack { ForEach(0.. index in Text(verbatim: index.formatted()) .id(index) } } .scrollTargetLayout() } .scrollTargetBehavior(.viewAligned) .scrollPosition(id: $position) } } @State private var position = ScrollPosition(edge: .top) var body: some View { ScrollView { Button("Scroll to bottom") { position.scrollTo(edge: .bottom) } ForEach(1.. index in Text(verbatim: index.formatted()) .id(index) } Button("Scroll to top") { position.scrollTo(edge: .top) } } .scrollPosition($position) } } @State private var position = ScrollPosition(edge: .top) var body: some View { ScrollView { Button("Random Scroll") { let id = (1.. index in Text(verbatim: index.formatted()) .id(index) } } .scrollPosition($position) .animation(.default, value: position) } } @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.. index in Text(verbatim: index.formatted()) .id(index) } } .scrollPosition($position) .animation(.default, value: position) } } position.scrollTo(y: 100) position.scrollTo(x: 200) } @State private var position = ScrollPosition(edge: .top) @State var curPosID: Int? @State var offsetY: CGFloat? var body: some View { ScrollView { ForEach(1.. index in Text(verbatim: index.formatted()) .font(.largeTitle.weight(.heavy)) .padding() .id(index) } .scrollTargetLayout() } .scrollPosition(id: $curPosID) .scrollPosition($position) .animation(.default, value: position) .safeAreaInset(edge: .bottom) { Button("Random Scroll") { let id = (1.. old,new in print("用代码滚动视图的ID: \(new.viewID)") curPosID = new.viewID as? Int } .onChange(of: curPosID) { _,new in print("实时滚动视图的 ID: \(new)") } } } @State private var position = ScrollPosition(edge: .top) var body: some View { ScrollView { ForEach(1.. index in Text(verbatim: index.formatted()) .font(.largeTitle.weight(.heavy)) .padding() .id(index) } } .scrollPosition($position) .animation(.default, value: position) .safeAreaInset(edge: .bottom) { Button("Random Scroll") { let id = (1.. old,new in print("是否由用户拖动引起的滚动:\(new.isPositionedByUser ? "是" : "否")") } } } old,new in print("当前内容滚动偏移:\(new.point)") } @State private var position = ScrollPosition(edge: .top) @State var curPosID: Int? @State var offsetY: CGFloat? var body: some View { ScrollView { ForEach(1.. index in Text(verbatim: index.formatted()) .font(.largeTitle.weight(.heavy)) .padding() .id(index) } .scrollTargetLayout() } .scrollPosition(id: $curPosID) .scrollPosition($position) .animation(.default, value: position) .safeAreaInset(edge: .bottom) { Button("Random Scroll") { let id = (1.. old,new in print("用代码滚动视图的ID: \(new.viewID)") curPosID = new.viewID as? Int } .onChange(of: curPosID) { _,new in print("实时滚动视图的 ID: \(new)") } .onScrollGeometryChange(for: CGFloat.self, of: { geo in geo.contentOffset.y }, action: { old, new in offsetY = new }) .onChange(of: offsetY) { _, new in guard let new else { return } print("当前 y 轴滚动偏移:\(new.formatted())") } } }
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。