切換語言為:簡體

掌握 SwiftUI 中的 ScrollView:滾動幾何

  • 爱糖宝
  • 2024-07-08
  • 2047
  • 0
  • 0

前言

本文探討了如何使用 onScrollGeometryChange 檢視修飾符有效地監控和管理滾動位置和幾何。透過詳細的程式碼示例和解釋,你將學習如何利用這些工具建立動態和響應迅速的用戶界面。

SwiftUI 是一個強大的框架,它簡化了在蘋果平臺上構建用戶界面的過程。SwiftUI 中的一個基本元件是 ScrollView,它允許使用者透過滾動導航內容。然而,管理滾動位置和理解滾動互動可能是一個挑戰。ScrollGeometry 和 onScrollGeometryChange 檢視修飾符的引入解決了這些挑戰,為開發者提供了更多的控制和對滾動行為的深入瞭解。

什麼是 ScrollPosition

ScrollPosition 是一種型別,允許開發者以程式設計方式讀取或更改滾動位置。雖然有用,但當用戶使用手勢與滾動檢視互動時,它顯得不夠全面。以下是一個展示 ScrollPosition 使用的示例:

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)
    }
}

在這個示例中,我們將滾動檢視繫結到一個狀態屬性。當按下按鈕時,滾動檢視會將其內容偏移移動到指定點。然而,我們無法讀取使用者透過手勢互動設定的具體內容偏移。

引入 ScrollGeometry

SwiftUI 的新 ScrollGeometry 型別以及 onScrollGeometryChange 檢視修飾符提供了一個解決方案。這些工具允許開發者在使用者互動期間準確讀取內容偏移。

使用 onScrollGeometryChange

讓我們探索如何使用 onScrollGeometryChange 檢視修飾符與 ScrollGeometry:

struct ContentView: View {
    @State private var scrollPosition = ScrollPosition(y: 0)
    @State private var offsetY: CGFloat = 0
    
    var body: some View {
        ScrollView {
            ForEach(1..<100, id: \.self) { number in
                Text(verbatim: number.formatted())
                    .id(number)
            }
        }
        .scrollPosition($scrollPosition)
        .onScrollGeometryChange(for: CGFloat.self) { geometry in
            geometry.contentOffset.y
        } action: { oldValue, newValue in
            if oldValue != newValue {
                offsetY = newValue
            }
        }
        .onChange(of: offsetY) {
            print(offsetY)
        }
    }
}

onScrollGeometryChange 檢視修飾符接受三個引數:

  1. 型別引數:指定要跟蹤的滾動幾何型別。在此示例中,我們使用 CGFloat 來跟蹤內容偏移的 Y 軸。

  2. 轉換閉包:從 ScrollGeometry 例項中提取所需資訊。

  3. 動作閉包:處理滾動幾何的變化,透過比較舊值和新值,允許我們相應地更新狀態屬性。

高階滾動幾何跟蹤

ScrollGeometry 提供了許多有價值的屬性,如內容偏移、邊界、容器大小、可見矩形、內容插入和內容大小。開發者可以提取單個屬性或組合多個屬性以獲得全面的見解。

以下是一個結合內容大小和可見矩形跟蹤的示例:

struct ContentView: View {
    struct ScrollData: Equatable {
        let size: CGSize
        let visible: CGRect
    }
    
    @State private var scrollPosition = ScrollPosition(y: 0)
    @State private var scrollData = ScrollData(size: .zero, visible: .zero)
    
    var body: some View {
        ScrollView {
            ForEach(1..<100, id: \.self) { number in
                Text(verbatim: number.formatted())
                    .id(number)
            }
        }
        .scrollPosition($scrollPosition)
        .onScrollGeometryChange(for: ScrollData.self) { geometry in
            ScrollData(size: geometry.contentSize, visible: geometry.visibleRect)
        } action: { oldValue, newValue in
            if oldValue != newValue {
                scrollData = newValue
            }
        }
        .onChange(of: scrollData) {
            print(scrollData)
        }
    }
}

在這個示例中,我們定義了一個 ScrollData 結構來儲存大小和可見矩形屬性。在使用 onScrollGeometryChange 檢視修飾符時,我們將 ScrollData 作為轉換閉包的返回型別,從 ScrollGeometry 例項中提取所有所需的資料。

完整程式碼示例分析

下面是一個完整的 SwiftUI Demo,其中包含了我們剛剛討論的 ScrollView、ScrollGeometry 和 onScrollGeometryChange 的使用示例。你可以在 Xcode 中執行這個專案來觀察其效果。

完整程式碼示例

import SwiftUI

struct ContentView: View {
    @State private var scrollPosition = ScrollPosition(y: 0)
    @State private var offsetY: CGFloat = 0
    
    var body: some View {
        VStack {
            Text("Scroll Offset: \(offsetY, specifier: "%.2f")")
                .padding()
            
            ScrollView {
                ForEach(1..<100, id: \.self) { number in
                    Text(verbatim: number.formatted())
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color(.secondarySystemBackground))
                        .cornerRadius(8)
                        .padding(.horizontal)
                        .id(number)
                }
            }
            .scrollPosition($scrollPosition)
            .onScrollGeometryChange(for: CGFloat.self) { geometry in
                geometry.contentOffset.y
            } action: { oldValue, newValue in
                if oldValue != newValue {
                    offsetY = newValue
                }
            }
            .onChange(of: offsetY) {
                print(offsetY)
            }
        }
    }
}

struct ScrollData: Equatable {
    let size: CGSize
    let visible: CGRect
}

struct AdvancedContentView: View {
    @State private var scrollPosition = ScrollPosition(y: 0)
    @State private var scrollData = ScrollData(size: .zero, visible: .zero)
    
    var body: some View {
        VStack {
            Text("Content Size: \(scrollData.size.width, specifier: "%.2f") x \(scrollData.size.height, specifier: "%.2f")")
                .padding()
            Text("Visible Rect: \(scrollData.visible.origin.x, specifier: "%.2f"), \(scrollData.visible.origin.y, specifier: "%.2f") - \(scrollData.visible.width, specifier: "%.2f") x \(scrollData.visible.height, specifier: "%.2f")")
                .padding()
            
            ScrollView {
                ForEach(1..<100, id: \.self) { number in
                    Text(verbatim: number.formatted())
                        .padding()
                        .frame(maxWidth: .infinity)
                        .background(Color(.secondarySystemBackground))
                        .cornerRadius(8)
                        .padding(.horizontal)
                        .id(number)
                }
            }
            .scrollPosition($scrollPosition)
            .onScrollGeometryChange(for: ScrollData.self) { geometry in
                ScrollData(size: geometry.contentSize, visible: geometry.visibleRect)
            } action: { oldValue, newValue in
                if oldValue != newValue {
                    scrollData = newValue
                }
            }
            .onChange(of: scrollData) {
                print(scrollData)
            }
        }
    }
}

@main
struct ScrollViewDemoApp: App {
    var body: some Scene {
        WindowGroup {
            TabView {
                ContentView()
                    .tabItem {
                        Label("Basic", systemImage: "1.square.fill")
                    }
                
                AdvancedContentView()
                    .tabItem {
                        Label("Advanced", systemImage: "2.square.fill")
                    }
            }
        }
    }
}

如何執行

  1. 開啟 Xcode 並建立一個新的 SwiftUI 專案。

  2. 將預設生成的 ContentView.swift 檔案替換為上面的完整程式碼。

  3. @main 註釋下的應用程式入口點中,確保你的主檢視是 ScrollViewDemoApp

  4. 執行專案。

功能解釋

  • ContentView: 展示基本的滾動偏移追蹤功能,透過 onScrollGeometryChange 檢視修飾符追蹤 Y 軸的內容偏移。

  • AdvancedContentView: 展示更高階的滾動幾何追蹤功能,追蹤內容大小和可見矩形的變化。

  • ScrollViewDemoApp: 包含 TabView,方便在基本和高階示例之間切換。

總結

今天,我們探討了 SwiftUI 中的新 ScrollGeometry 型別和 onScrollGeometryChange 檢視修飾符。這些工具為開發者提供了對滾動位置和互動的精確控制和洞察,增強了動態和響應迅速的用戶界面的開發。透過利用這些功能,你可以建立更具吸引力和直觀的應用程式。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.