In Swift UI of iOS14, scrolling process of list can be controlled by code

Introduction

You can now scroll the list with code that you couldn't do with the Swift UI in iOS 13 in iOS 14. This article summarizes how to implement it.

Scrolling on iOS 13

It is a process of "automatically scrolling to the ○ th cell" that is common when developing iOS apps, There was no way to do this with the Swift UI on iOS 13.

struct ContentView: View {
    var body: some View {
        List(0..<100) {
            Text("\($0)")
        }
    }
}

In UIKit, you can easily implement the corresponding requirement by calling methods such as scrollToRow, scrollToItem in the following form. I often used ʻUIViewRepresentable, ʻUIViewControllerRepresentable, etc. just for this feature.

// UITableView
tableView.scrollToRow(at: IndexPath(row: 10, section: 0),
                      at: .top,
                      animated: true)

// UICollectionView
collectionView.scrollToItem(at: .init(item: 10, section: 0), 
                            at: .top,
                            animated: true)

Scrolling on iOS14

iOS14 has added a new View called ScrollViewReader that can control the scrolling state. By using this, it is possible to implement the process of automatically scrolling to any point.

The sample code is shown below.

Preparation

First, set up a TextField to specify the scroll point as a preparation.

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        VStack {
            HStack {
                TextField("input row number", text: $text)
                Button("Scroll") {
                    guard let row = Int(text) else { return }
                    print(row)
                }
            }.padding()
            
            List(0..<100) {
                Text("\($0)")
            }
        }
    }
}

This allows you to tap the button to get the value that the user has entered in the TextField.

Scroll control

Then, it is the implementation part of the process of actually scrolling.

First, enclose the part you want to scroll control with ScrollViewReader. This makes it possible to get a ScrollViewProxy instance that can be scroll-controlled.

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        VStack {
+           ScrollViewReader { (proxy: ScrollViewProxy) in
                HStack {
                    TextField("input row number", text: $text)
                    Button("Scroll") {
                        guard let row = Int(text) else { return }
                    }
                }.padding()
                List(0..<100) {
                    Text("\($0)")
                }
+           }
        }
    }
}

Furthermore, an ID for specifying the scroll position is assigned to the target View, and Scrolling can be achieved simply by specifying the ID in the scrollTo method of ScrollViewProxy at the part where you want to start control.

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        VStack {
            ScrollViewReader { (proxy: ScrollViewProxy) in
                HStack {
                    TextField("input row number", text: $text)
                    Button("Scroll") {
                        guard let row = Int(text) else { return }
+                       withAnimation {
+                           proxy.scrollTo(row, anchor: .top)
+                       }
                    }
                }.padding()

                List(0..<100) {
                    Text("\($0)")
+                       .id($0)
                }
            }
        }
    }
}

Now you can do what you want to do.

By the way, if you do not add withAnimation, scrolling animation will not be performed. In addition, the position after scrolling can be specified in detail with the second argument ʻanchor`.

bonus

It worked fine for Grid as well.

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        VStack {
            ScrollViewReader { (proxy: ScrollViewProxy) in
                HStack {
                    TextField("input row number", text: $text)
                    Button("Scroll") {
                        guard let row = Int(text) else { return }
                        withAnimation {
                            proxy.scrollTo(row, anchor: .top)
                        }
                    }
                }.padding()
                
                ScrollView {
                    LazyVGrid(
                        columns: [
                            GridItem(.flexible(minimum: 0, maximum: .infinity)),
                            GridItem(.flexible(minimum: 0, maximum: .infinity)),
                        ],
                        alignment: .center,
                        spacing: nil
                    ) {
                        ForEach(0..<100) {
                            Text("\($0)")
                                .frame(height: 100)
                        }
                    }
                }
            }
        }
    }
}

Recommended Posts

In Swift UI of iOS14, scrolling process of list can be controlled by code
List of devices that can be previewed in Swift UI
SKStoreReviewController implementation memo in Swift UI of iOS14
Things to be aware of when writing code in Java
How to change BackgroundColor etc. of NavigationBar in Swift UI
How to make a key pair of ecdsa in a format that can be read by Java
[Swift] I tried using ColorPicker (the one that can easily select colors) added in iOS14 [Swift UI]