Reading time: 1 min

This quick recipe shows how to scroll a SwiftUI ScrollView programatically when the scrolling trigger is not wrapped in a ScrollViewReader. E.g, the image below shows scrolling triggered from navigation bar buttons:

preview

As you know, you can scroll a ScrollView programatically using a ScrollViewReader and its ScrollViewProxy. Triggering a scroll (say, on a button click) from view within the ScrollViewReader is easy, but what if you want it to happen outside of it?

The trick is to:

  1. Extract the scrolling target (the ID of the view in the ScrollView that you wish to scroll to) in an optional @State var.
  2. Trigger the scroll by setting its value.
  3. Register its value change with onChange(of:) modifier, attached to the scroll view content (which has access to a ScrollViewProxy).

Here's the full code:

struct ScrollFromOutside: View {
  private let items = Array(1...100)

  // if not nil, determines what the proxy should scroll to
  @State private var scrollTarget: Int?

  var body: some View {
    NavigationView {
      ScrollView {
        ScrollViewReader { proxy in
          LazyVStack {
            ForEach(items, id: \.self) { item in
              Text("Row \(item)")
                .id(item)
            }
          }
          // registers the change of scroll target and triggers the scroll
          .onChange(of: scrollTarget) { newValue in
            if let target = newValue {
              scrollTarget = nil // allows for future triggers
              withAnimation {
                proxy.scrollTo(target)
              }
            }
          }
        }
      }
      .navigationBarTitle("Scroll from outside", displayMode: .inline)
      .navigationBarItems(leading: Button("To top") {
        scrollTarget = items.first // triggers scroll
      }, trailing: Button("To bottom") {
        scrollTarget = items.last // triggers scroll
      })
    }
  }
}

Next Post Previous Post