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 中文版
如何在 SwiftUI App 中開發 Live Activities
SwiftUI 框架

如何在 SwiftUI App 中開發 Live Activities

Live Activities 首次於 iOS 16 推出,是 Apple 最令人興奮的更新之一,能讓 App 與使用者在即時互動上更有連結。它不再需要使用者不斷打開 App,Live Activities 可以讓資訊直接顯示在鎖定畫面和 Dynamic Island 上。
使用 Tool Calling 強化 Foundation Models 功能
AI

使用 Tool Calling 強化 Foundation Models 功能

在前幾篇教學中,我們介紹了 Foundation Models 在 iOS 26 中的運作方式,以及如何使用這個全新框架打造具備 AI 功能的應用。我們也介紹了 @Generable 巨集,它能輕鬆地將模型回應轉換為結構化的 Swift 類型。 現在,在這個 Foundation
活用 Foundation Models 的 @Generable 與 @Guide 製作測驗 App
AI

活用 Foundation Models 的 @Generable 與 @Guide 製作測驗 App

在前一篇教學中,我們介紹了 Foundation Models 框架,並示範了如何用它來進行基本的內容生成。那個過程相當簡單——你提供一個提示詞(prompt),等幾秒鐘,就能獲得自然語言的回應。在我們的範例中,我們建立了一個簡單的問答 App,讓使用者可以提問,App 則直接顯示生成的文字。 但如果回應變得更複雜——你需要把非結構化文字轉換為結構化的物件呢? 舉例來說,
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。