UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Have a button outside of ScrollViewReader be able to scroll the ScrollView to a position?

Forums > SwiftUI

Hey everybody,

In the new version of SwiftUI we're able to scroll to a position in the ScrollView by using a ScrollViewReader and it's value property. However, I want to be able to have a button outside of the ScrollViewReader that when tapped can scroll the ScrollView to the correct position.

I've been trying to find a solution and can't come up with one. Does anybody know how to make this happen?

Thanks in advance for the help.

3      

Hey Justin,

The trick here is extracting the storage of the target outside of the ScrollView. The following approach is easiest within a single View struct, but it can be extended to work across different View structs by using Bindings.

Start off by defining a store property on your struct that holds an optional reference of the target identifier. I'll use Ints, but you can use anything that conforms to the Hashable protocol - Strings, Enum cases etc...

struct ContentView: View {
    @State private var scrollTarget: Int?
}

Then set up the skeleton UI. I'll have my main scrollview take up most of the space, but then I'll have a horizontally scrolling row of buttons along the bottom of the screen that scroll to different children of the ScrollView.

struct ContentView: View {
    @State private var scrollTarget: Int?

    var body: some View {
        NavigationView {
            VStack {
                ScrollView {
                    ScrollViewReader { (proxy: ScrollViewProxy) in
                        VStack {
                            ForEach(0..<100) { i in
                                Text("Row \(i)")
                                    .frame(height: 40)
                                    .id(i) // Uniquely identify each row in the scrollview by its index
                            }
                        }
                        .frame(maxWidth: .infinity)
                        .padding()
                        // Scroll to the desired row when the @State variable changes
                    }
                }
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack {
                        ForEach(0..<10) { i in
                            Button("Row \(i * 10)") {
                                // Store the desired scroll index
                            }
                        }
                    }
                    .padding(.horizontal)
                    .padding(.vertical, 4)
                }
            }
            .navigationTitle("Scroll Example")
        }
    }
}

Last but not least we need to make use of the property we defined earlier, as well as SwiftUI's new onChange(of:perform:) modifier to scroll to the correct view when the variable changes.

struct ContentView: View {
    @State private var scrollTarget: Int?

    var body: some View {
        NavigationView {
            VStack {
                ScrollView {
                    ScrollViewReader { (proxy: ScrollViewProxy) in
                        VStack {
                            ForEach(0..<100) { i in
                                Text("Row \(i)")
                                    .frame(height: 40)
                                    .id(i) // Uniquely identify each row in the scrollview by its index
                                }
                            }
                        }
                        .frame(maxWidth: .infinity)
                        .padding()
                        // Scroll to the desired row when the @State variable changes
                        .onChange(of: scrollTarget) { target in
                        if let target = target {
                            scrollTarget = nil

                            withAnimation {
                                proxy.scrollTo(target, anchor: .center)
                            }
                        }
                    }
                }
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack {
                        ForEach(0..<10) { i in
                            Button("Row \(i * 10)") {
                                // Store the desired scroll index
                                scrollTarget = i * 10
                            }
                        }
                    }
                    .padding(.horizontal)
                    .padding(.vertical, 4)
                }
            }
            .navigationTitle("Scroll Example")
        }
    }
}

Hope that helps!

--Jakub

6      

Works like a charm. Thank you so much!

4      

@jakcharvat This was incredibly helpful. Thank you for posting this detailed solution.

4      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.