SwiftUI 框架

在 SwiftUI 處理 ScrollView:簡單以編程方式滾動到特定行列

在 SwiftUI 中,即使是執行簡單的任務時,處理 ScrollView 的步驟都可以變得十分繁瑣;更令人沮喪的是,同一個情況在 UIKit 明明就非常簡單。在這篇文章中,Alessandro 會帶大家實作一個簡單的範例 App,只需要 30 多行程式碼,就可以以編程方式滾動列表,到我們所需要的行列了。
在 SwiftUI 處理 ScrollView:簡單以編程方式滾動到特定行列
在 SwiftUI 處理 ScrollView:簡單以編程方式滾動到特定行列
In: SwiftUI 框架
本篇原文(標題:SwiftUI: How to Programmatically Scroll to a Row)刊登於作者 Medium,由 Alessandro Manilii 所著,並授權翻譯及轉載。

SwiftUI 中,即使是執行簡單的任務時,處理 ScrollView 的步驟都可以變得十分繁瑣;更令人沮喪的是,同一個情況在 UIKit 明明就非常簡單。

不幸的是,其中一個繁瑣的步驟,就是滾動到所需的行列。

在 UIKit 中,每個 UIScrollView 都有一個非常方便的方法,來以編程方式滾動視圖:

func setContentOffset(CGPoint, animated: Bool)

同樣地,每種子類都有相似的方法。例如,我們可以在 UITableView 中使用:

func scrollToRow(at: IndexPath, at: UITableView.ScrollPosition, animated: Bool)

然而,目前 SwiftUI 還沒有這些簡單的功能,因此我們就需要編寫一些比較複雜的程式碼,來執行相同的任務。

在這篇文章中,我們將會一起實作一個這樣的範例 App:

首先,讓我們編寫出簡單的 UI,我們只需要用到 ListPicker 視圖。

struct ContentView: View {

    @State var selectedIndex: Int = 0

    var body: some View {

        NavigationView {
            VStack {

                List {
                    ForEach(0..<100, id:\.self) { index in
                        Text("Row number \(index)")
                            .padding()
                    }
                }

                Divider()

                Picker("", selection: $selectedIndex) {
                    ForEach(0..<100, id: \.self) { index in
                        Text("Go to Row \(index)")
                    }
                }
                .pickerStyle(.wheel)
            }
            .navigationTitle("Scroll To Demo")
        }
    }
}

當然,這個簡單的 UI 是無法操作的。我們可以試著 wheel picker 選擇一個項目,你會發現它沒有回應。

我們需要一些元件,來觀察及讀取 ScrollView 內發生的事情。

Apple 在 iOS 14 中引入了一個名為 ScrollViewProxystruct,它的定義是這樣的:

掃描 Proxy 的所有滾動視圖 (scroll view),找出第一個有標識符 (identifier) `id` 的子視圖,然後滾動到該視圖。

在 SwiftUI 官方文檔的同一部分,我們也看到:

我們不會直接創建 ScrollViewProxy 的實例。相反,ScrollViewReader 會在其內容視圖構建器中接收 ScrollViewProxy 的實例。

因此,我們會按 Apple 所說,使用 ScrollViewReader 視圖!

這個新的元件是一個內建 proxy scrollView 的 View。讓我們這樣開始修改 UI:

struct ContentView: View {

    @State var selectedIndex: Int = 0

    var body: some View {
        VStack {
            NavigationView {
            ScrollViewReader { scrollView in
                List {
                    ForEach(0..<100, id:\.self) { index in
                        Text("Row number \(index)").id(index) // <- NOTE HERE
                            .padding()
                    }
                }
                Divider()

                Picker("", selection: $selectedIndex) {
                    ForEach(0..<100, id: \.self) { index in
                        Text("Go to Row \(index)")
                    }
                    
                    // More to come...
                }
                .pickerStyle(.wheel)
            }
            .navigationTitle("Scroll To Demo")
            }
        }
    }
}

從上面的程式碼,我們做了以下的事:

  • 在第 8 行,我們使用了 ScrollViewReader 視圖。
  • 在第 11 行,我們在 Text 視圖添加了 .id(index) 修飾符。在 SwiftUI 中,我們經常會用一個獨特的 id 來標識一個元素;這正是我們在這行程式碼所做的事。

現在,讓我們編寫程式碼,來以編程方式滾動列表吧!我在第 22 行添加了註解 // More to come...,我們將在這裡綁定 picker 的選擇和操作。

讓我們加入以下程式碼:

.onReceive(Just(selectedIndex)) { value in
    scrollView.scrollTo(selectedIndex, anchor: .center)
}

由於 Just 運算符 (operator) 是來自 Combine 框架的,因此我們需要在程式碼開頭匯入這個框架:#import Combine

完成了!

現在來當我們選擇一個項目時,上方的 List 就會滾動到指定的行列。

不過你可能也覺得,這段程式碼雖然可以運作,但還是不夠好,那個「跳轉」影響了整個 UX。

讓我們為滾動動作添加一個簡單而好看的動畫:

.onReceive(Just(selectedIndex)) {value in
            withAnimation {
                scrollView.scrollTo(
                    selectedIndex, anchor: .center
                )
            }
        }

範例 App 完成了!以下是完整的程式碼:

import SwiftUI
import Combine

struct ContentView: View {

    @State var selectedIndex: Int = 0

    var body: some View {
        VStack {
            NavigationView {
            ScrollViewReader { scrollView in
                List {
                    ForEach(0..<100, id:\.self) { index in
                        Text("Row number \(index)").id(index)
                            .padding()
                    }
                }
                              
                Divider()

                Picker("", selection: $selectedIndex) {
                    ForEach(0..<100, id: \.self) { index in
                        Text("Go to Row \(index)")
                    }
                    .onReceive(Just(selectedIndex)) {value in
                        withAnimation {
                            scrollView.scrollTo(
                                selectedIndex, anchor: .center
                            )
                        }
                    }
                }
                .pickerStyle(.wheel)
            }
            .navigationTitle("Scroll To Demo")
            }
        }
    }
}

只需要 30 多行程式碼,我們就實作好需要的功能了。

如你所見,明白了操作的機制之後,整個實作過程其實非常簡單,只需要為要滾動到的項目添加一個 id 就可以了。

謝謝你的閱讀。如果你想看看這篇文章的教學影片,可以到我的 YouTube 頻道觀看:

本篇原文(標題:SwiftUI: How to Programmatically Scroll to a Row)刊登於作者 Medium,由 Alessandro Manilii 所著,並授權翻譯及轉載。
作者簡介:Alessandro Manilii,一位意大利的專業 iOS 開發者,Wakala 的 iOS 技術主管。如果你有興趣閱讀我的其他文章,可以在 Medium 上訂閱。
譯者簡介:Kelly Chan-AppCoda 編輯小姐。

作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。