如何使用 SwiftUI 中 ScrollView 的滾動(dòng)偏移
前言
WWDC 24 已經(jīng)結(jié)束,我決定開(kāi)始寫一些關(guān)于 SwiftUI 框架即將推出的新特性的文章。今年,蘋果繼續(xù)填補(bǔ)空白,引入了對(duì)滾動(dòng)位置更細(xì)粒度的控制。本周,我們將學(xué)習(xí)如何操作和讀取滾動(dòng)偏移。
使用 scrollPosition
SwiftUI 框架已經(jīng)允許我們通過(guò)視圖標(biāo)識(shí)符跟蹤和設(shè)置滾動(dòng)視圖的位置。這種方法效果不錯(cuò),但不足以更準(zhǔn)確地跟蹤用戶交互。
struct ContentView: View {
@State private var position: Int?
var body: some View {
ScrollView {
LazyVStack {
ForEach(0..<100) { index in
Text(verbatim: index.formatted())
.id(index)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $position)
}
}
在上面的代碼示例中,我們使用了視圖標(biāo)識(shí)符和 scrollPosition 修飾符來(lái)跟蹤和設(shè)置滾動(dòng)視圖的位置。雖然這種方法效果不錯(cuò),但在某些情況下,尤其是需要更精確的用戶交互跟蹤時(shí),它可能不夠用。為了彌補(bǔ)這一不足,SwiftUI 引入了新的 ScrollPosition 類型,使我們能夠通過(guò)偏移量、滾動(dòng)視圖的邊緣、視圖標(biāo)識(shí)符等組合滾動(dòng)位置。
新的 ScrollPosition 類型
SwiftUI 框架引入了新的 ScrollPosition 類型,使我們能夠通過(guò)偏移量、滾動(dòng)視圖的邊緣、視圖標(biāo)識(shí)符等組合滾動(dòng)位置。
struct ContentView: View {
@State private var position = ScrollPosition(edge: .top)
var body: some View {
ScrollView {
Button("Scroll to bottom") {
position.scrollTo(edge: .bottom)
}
ForEach(1..<100) { index in
Text(verbatim: index.formatted())
.id(index)
}
Button("Scroll to top") {
position.scrollTo(edge: .top)
}
}
.scrollPosition($position)
}
}
如上例所示,我們定義了 position 狀態(tài)屬性,并使用 scrollPosition 視圖修飾符將滾動(dòng)視圖與狀態(tài)屬性綁定。我們還放置了兩個(gè)按鈕,允許你快速滾動(dòng)到滾動(dòng)視圖中的第一個(gè)或最后一個(gè)項(xiàng)目。ScrollPosition 類型提供了許多重載的 scrollTo 函數(shù),使我們能夠處理不同的情況。
為滾動(dòng)添加動(dòng)畫
通過(guò)附加動(dòng)畫視圖修飾符并傳遞 ScrollPosition 類型的實(shí)例作為 value 參數(shù),我們可以輕松地為編程滾動(dòng)添加動(dòng)畫。
struct ContentView: View {
@State private var position = ScrollPosition(edge: .top)
var body: some View {
ScrollView {
Button("Scroll to bottom") {
position.scrollTo(edge: .bottom)
}
ForEach(1..<100) { index in
Text(verbatim: index.formatted())
.id(index)
}
Button("Scroll to top") {
position.scrollTo(edge: .top)
}
}
.scrollPosition($position)
.animation(.default, value: position)
}
}
滾動(dòng)到特定項(xiàng)目
我們添加了另一個(gè)按鈕來(lái)將滾動(dòng)視圖的位置更改為隨機(jī)項(xiàng)目。我們?nèi)匀皇褂?ScrollPosition 類型的 scrollTo 函數(shù),但我們提供了一個(gè)可哈希的標(biāo)識(shí)符。這個(gè)選項(xiàng)允許我們將位置更改為特定項(xiàng)目,通過(guò)使用 anchor 參數(shù),我們可以選擇所選視圖的哪個(gè)點(diǎn)應(yīng)該可見(jiàn)。
struct ContentView: View {
@State private var position = ScrollPosition(edge: .top)
var body: some View {
ScrollView {
Button("Scroll somewhere") {
let id = (1..<100).randomElement() ?? 0
position.scrollTo(id: id, anchor: .center)
}
ForEach(1..<100) { index in
Text(verbatim: index.formatted())
.id(index)
}
}
.scrollPosition($position)
.animation(.default, value: position)
}
}
滾動(dòng)到特定偏移
最后但同樣重要的是 scrollTo 函數(shù)的 point 參數(shù)重載,允許我們傳遞 CGPoint 實(shí)例以將視圖滾動(dòng)到內(nèi)容的特定點(diǎn)。
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)
}
}
如上例所示,我們使用帶有 CGPoint 參數(shù)的 scrollTo 函數(shù)。它還提供重載,允許我們僅按 X 或 Y 軸滾動(dòng)視圖。
struct ContentView: View {
@State private var position = ScrollPosition(edge: .top)
var body: some View {
ScrollView {
Button("Scroll to offset") {
position.scrollTo(y: 100)
position.scrollTo(x: 200)
}
ForEach(1..<100) { index in
Text(verbatim: index.formatted())
.id(index)
}
}
.scrollPosition($position)
.animation(.default, value: position)
}
}
讀取滾動(dòng)位置
我們學(xué)習(xí)了如何使用新的 ScrollPosition 類型操作滾動(dòng)位置,這也允許我們讀取滾動(dòng)視圖的位置。ScrollPosition 提供了可選的 edge、point 和 viewID 屬性,以在你編程滾動(dòng)時(shí)讀取值。
每當(dāng)用戶與滾動(dòng)視圖交互時(shí),這些屬性將變?yōu)?nil。ScrollPosition 類型上的 isPositionedByUser 屬性允許我們了解何時(shí)用戶手勢(shì)移動(dòng)滾動(dòng)視圖內(nèi)容。
提供一個(gè)可以運(yùn)行示例:
下面是一個(gè)可以運(yùn)行的示例代碼,演示如何讀取和顯示滾動(dòng)視圖的位置。我們將使用一個(gè) Text 視圖來(lái)顯示當(dāng)前滾動(dòng)位置:
import SwiftUI
struct ContentView: View {
@State private var position = ScrollPosition(edge: .top)
@State private var scrollOffset: CGPoint?
var body: some View {
VStack {
ScrollView {
LazyVStack {
ForEach(0..<100) { index in
Text("Item \(index)")
.id(index)
.padding()
.background(Color.yellow)
.cornerRadius(10)
.padding(.horizontal)
}
}
.scrollPosition($position)
.onScrollGeometryChange { geometry in
scrollOffset = geometry?.contentBounds.origin
}
}
.animation(.default, value: position)
if let offset = scrollOffset {
Text("Scroll Offset: x = \(Int(offset.x)), y = \(Int(offset.y))")
.padding()
} else {
Text("Scroll Offset: not available")
.padding()
}
}
.padding()
}
}
在這個(gè)示例中,我們使用了 onScrollGeometryChange 修飾符來(lái)讀取滾動(dòng)視圖的幾何變化。每當(dāng)滾動(dòng)視圖滾動(dòng)時(shí),geometry?.contentBounds.origin 將提供當(dāng)前滾動(dòng)位置的偏移量。我們將這個(gè)偏移量存儲(chǔ)在 scrollOffset 狀態(tài)屬性中,并在視圖底部顯示當(dāng)前的滾動(dòng)位置。
總結(jié)
在本文中,我們深入探討了 SwiftUI 框架中 ScrollView 的新特性,特別是如何通過(guò) ScrollPosition 類型實(shí)現(xiàn)更精確的滾動(dòng)控制。我們介紹了如何使用 ScrollPosition 類型進(jìn)行滾動(dòng)位置的設(shè)置和讀取,包括使用偏移量、視圖標(biāo)識(shí)符等方式進(jìn)行操作。此外,我們還展示了如何通過(guò)動(dòng)畫和事件處理來(lái)增強(qiáng)用戶體驗(yàn)。通過(guò)這些新功能,開(kāi)發(fā)者可以更靈活地控制滾動(dòng)視圖的行為,從而創(chuàng)建更加流暢和直觀的用戶界面。希望這些內(nèi)容對(duì)你有所幫助。