利用 ScrollViewReader 輕鬆讓滾動視圖滾動到特定位置


在新版 SwiftUI 中,其中一個我最喜歡的新功能就是 ScrollViewReader。在 iOS 14 推出之前,要控制內置 ScrollView 的滾動位置並不容易。如果想要滾動視圖滾動到特定位置,就需要自己想出解決方案。

有了 ScrollViewReader 之後,我們只需要幾行程式碼,就可以使滾動視圖滾動到特定位置。在這篇教學文章中,我們會探索這個新視圖元件,讓大家了解如何把它應用在 App 上。

創建一個水平的 ScrollView

現在,讓我們從一個簡單的範例看看 ScrollViewReader 的用法。先看看以下的程式碼:

struct ContentView: View {

    let photos = [ "bigben", "bridge", "canal", "effieltower", "flatiron", "oia" ]

    var body: some View {
        GeometryReader { geometry in
            ScrollView(.horizontal) {
                HStack(alignment: .center) {
                    ForEach(photos.indices) { index in
                        Image(photos[index])
                            .resizable()
                            .scaledToFill()
                            .frame(width: geometry.size.width - 50)
                            .cornerRadius(25)
                            .padding(.horizontal, 25)
                    }
                }
            }
        }
    }
}

我們建立了一個水平滾動視圖,來顯示一組照片。如果你在模擬器或預覽面板中執行程式碼,就可以水平滑動以滾動瀏覽照片。

scrollviewreader-app-demo

如果我們現在想添加一些按鈕,讓使用者瀏覽不同的照片,應該怎樣做呢?或者這樣說,我們如何讓滾動視圖滾動到特定圖像?

使用 ScrollViewReader

ScrollViewReader 就是上面問題的解決方法!這個新視圖元件的用法非常簡單。首先,給滾動視圖中的每個元素一個獨特的 ID。在這個範例中,我們可以將 .id 修飾符加到 Image 視圖,並將其數值設置為照片的索引 (index):

Image(photos[index])
    .resizable()
    .scaledToFill()
    .frame(width: geometry.size.width - 50)
    .cornerRadius(25)
    .padding(.horizontal, 25)
    .id(index)

接下來,讓我們利用 ScrollViewReader 包裝滾動視圖。ScrollViewReader 的內容視圖建構器接收 ScrollViewProxy 實例:

GeometryReader { geometry in
    VStack {
        ScrollViewReader { scrollView in
            ScrollView(.horizontal) {
                HStack(alignment: .center) {
                    ForEach(photos.indices) { index in
                        Image(photos[index])
                            .resizable()
                            .scaledToFill()
                            .frame(width: geometry.size.width - 50)
                            .cornerRadius(25)
                            .padding(.horizontal, 25)
                            .id(index)
                    }
                }
            }

            // Navigation Buttons
            HStack {
                // To be complete
            }
            .frame(height: 70)
        }
    }
}

在我們得到 Proxy 後,可以呼叫 scrollTo 函式,以滾動到特定索引。以下面這行程式碼為例,我們要求滾動視圖滾動到最後一張照片:

scrollView.scrollTo(photos.count - 1)

最後,我們可以聲明一個狀態變數 (state variable),來追踪當前的照片索引:

@State private var currentIndex = 0

然後,你可以這樣在 HStack 中編寫程式碼:

HStack {
    Button(action: {
        withAnimation {
            scrollView.scrollTo(0)
        }
    }) {
        Image(systemName: "backward.end.fill")
            .font(.system(size: 50))
            .foregroundColor(.black)
    }

    Button(action: {
        withAnimation {
            currentIndex = (currentIndex == 0) ? currentIndex : currentIndex - 1
            scrollView.scrollTo(currentIndex)
        }
    }) {
        Image(systemName: "arrowtriangle.backward.circle")
            .font(.system(size: 50))
            .foregroundColor(.black)
    }

    Button(action: {
        withAnimation {
            currentIndex = (currentIndex == photos.count - 1) ? currentIndex : currentIndex + 1
            scrollView.scrollTo(currentIndex)
        }
    }) {
        Image(systemName: "arrowtriangle.forward.circle")
            .font(.system(size: 50))
            .foregroundColor(.black)
    }

    Button(action: {
        withAnimation {
            scrollView.scrollTo(photos.count - 1)
        }
    }) {
        Image(systemName: "forward.end.fill")
            .font(.system(size: 50))
            .foregroundColor(.black)
    }
}
.frame(height: 70)

對於每個按鈕,我們都會呼叫 scrollTo 函式來滾動到特定的照片。利用 withAnimation 包裝程式碼後,App 會呈現一個漂亮的滾動動畫。

scrollviewreader-perform-scrolling

在 List 上應用

ScrollViewReader 不僅可以用來包裝 ScrollView,在 List 上也可以應用。假設我們把 App 轉換為列表形式,而不是水平滾動視圖:

ScrollViewReader { scrollView in
    List {
        ForEach(photos.indices) { index in
            Image(photos[index])
                .resizable()
                .scaledToFill()
                .cornerRadius(25)
                .id(index)
        }
    }
 
    .
    .
    .
}

我們還是可以應用 scrollTo 函式,要求列表滾動到特定元素。

總結

ScrollViewReader 是 SwiftUI 框架中很好的新增功能。現在我們不再需要開發自己的解決方案,就可以輕鬆地指示任何滾動視圖滾動到特定位置。希望你喜歡這篇教學文章,並看到這個新元件的用處。如果想更深入學習 SwiftUI 框架,並了解其他技巧,可以閱讀我們《精通 SwiftUI》一書。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。
原文How to Use ScrollViewReader to Perform Programmatic Scrolling


軟件工程師,AppCoda 創辦人。著有《iOS 13 App 程式設計實力超進化實戰攻略》、《iOS 13 App 程式設計實力超進化實戰攻略》以及《精通 SwiftUI》。曾任職於HSBC, FedEx等公司,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda,致力於iOS程式教學,產品設計及開發。

blog comments powered by Disqus
Shares
Share This