Scrolling With ScrollViewReader

How do you programmatically scroll a SwiftUI list to a specific item?

Scrolling A List To An Item

Here’s my SwiftUI challenge. I have a collection of country items that I’m showing in a List. I have a widget that shows a random country and launches the app, when tapped, so we can navigate to that country in the list:

struct CountryList: View {
  var countries: FetchedResults<Country>

  var body: some View {
    List {
      ForEach(countries) { country in
        NavigationLink(destination: CountryView(country: country)) {
          CountryCell(country: country)
        }
      }
    }
  }
}

To control the navigation programmatically I’ve created a State variable to store the identifier of the selected country:

@State private var selection: Country.ID?

I pass a binding to the state variable to the selection parameter of the navigation link and tag each country item with its identifier:

NavigationLink(destination: CountryView(country: country),
                       tag: country.id,
                 selection: $selection) { ... }

When I set the selection state variable to the identifier of a country the list navigates to the item tagged with that identifier. I trigger that in response to an open URL action:

List { ...
} 
.onOpenURL { url in
  if let id = Country.identifier(for: url) {
    selection = id
  }
}

If you try that the result will disappoint you. The navigation only seems to work if the destination is already onscreen. We can fix that by scrolling to the target country using a ScrollViewReader.

Using ScrollViewReader

A ScrollViewReader gives us a proxy for the scroll view that we can use to programmatically control the scroll. The setup is much like using a GeometryReader:

ScrollViewReader { proxy in
  List { ...
  }
}

I can then use the scrollTo method of proxy in my open URL handler to scroll to the target identifier:

.onOpenURL { url in
  if let id = Country.identifier(for: url) {
    proxy.scrollTo(id)
    selection = id
  }
}

Animating The Scroll

If you want to animate the scroll action, wrap it in a withAnimation function:

withAnimation {
  proxy.scrollTo(id)
}

Controlling Scroll Alignment

The scrollTo method takes a second optional anchor parameter to control the alignment of the identified view. If you don’t specify the anchor the scroll view scrolls the smallest amount possible to fully show the identified view.

For a vertical scroll view, using an anchor of top, center or bottom aligns the top, center of bottom of the identified view with the top, center or bottom of the scroll view (if possible):

proxy.scrollTo(country.id, anchor: .center)

The leading and trailing alignments work in the same way for a horizontal scroll view.