May 09, 2024 iOS

How to make a SwiftUI List scroll automatically?

Certainly! If you want to programmatically make a SwiftUI List move to show a specific row, you can achieve this by embedding the List inside a ScrollViewReaderThe ScrollViewReader provides a scrollTo() method on its proxy that allows you to specify the ID of the row you want to scroll to1. Here’s how you can do it:

import SwiftUI

struct ContentView: View {
    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                Button("Jump to #50") {
                    proxy.scrollTo(50)
                }
                List(0..<100, id: \.self) { i in
                    Text("Example \(i)").id(i)
                }
            }
        }
    }
}

In the example above, when you press the button labeled “Jump to #50,” the List will scroll directly to the row with ID 50. You can also provide an optional anchor parameter to control where the row should be positioned within the scroll view (e.g., proxy.scrollTo(50, anchor: .top)). If you call scrollTo() inside a withAnimation(), the movement will be animated1.

For iOS 13 and below, you can try a different approach by flipping the views. This starts the ScrollView at the bottom, and when adding new data to it, it automatically scrolls the view down. Rotate the outermost view with 180.rotationEffect(.radians(.pi)) and flip it across the vertical plane with .scaleEffect(x: -1, y: 1, anchor: .center)2.

Here are two effective ways to make a SwiftUI List scroll automatically in different scenarios:

1. Scrolling to the Bottom When New Items Are Added:

This approach is ideal for situations where you’re dynamically adding items to the list and want it to automatically scroll to the bottom to reveal the new content.

struct MyListView: View {
  @State private var items: [String] = []

  var body: some View {
    List {
      ForEach(items) { item in
        Text(item)
      }
      .onAppear { // Scroll to the bottom on initial rendering
        withAnimation {
          List(items) { item in
            Text(item)
          }
          .scrollTo(items.endIndex - 1) // Scroll to last item index
        }
      }
    }
    Button("Add Item") {
      items.append("New Item")
    }
  }
}

Explanation:

  • We use a @State variable items to store the list data.
  • Inside the List, we iterate through items using ForEach and display each item as Text.
  • The onAppear modifier is used to run code once when the list appears on the screen.
  • Inside onAppear, we create a nested List with the same data and use scrollTo with the index of the last item (items.endIndex - 1) to scroll to the bottom with animation.
  • When the “Add Item” button is tapped, a new item is appended to the items array, triggering a re-render and automatically scrolling to the bottom again.

2. Scrolling to a Specific Item:

If you want to programmatically scroll to a particular item in the list, you can use ScrollViewReader:

struct MyListView: View {
  @State private var items: [String] = ["Item 1", "Item 2", "Item 3"]
  @State private var selectedItem: String? = nil

  var body: some View {
    ScrollViewReader { reader in
      List {
        ForEach(items) { item in
          Text(item)
            .onTapGesture {
              selectedItem = item
            }
        }
        .id(item) // Assign unique ID for each item
      }
      .onAppear {
        if let selectedItem = selectedItem {
          withAnimation {
            reader.scrollTo(selectedItem) // Scroll to the selected item ID
          }
        }
      }
    }
    Button("Select Item 2") {
      selectedItem = "Item 2"
    }
  }
}

Explanation:

  • We use a ScrollViewReader to access the scroll view associated with the List.
  • Each item in the List has a unique id assigned using the item itself.
  • The onTapGesture modifier triggers when an item is tapped, setting the selectedItem state variable.
  • In the onAppear modifier, we check if selectedItem has a value. If so, we use reader.scrollTo(selectedItem) with animation to scroll to the item with the matching ID.
  • The button demonstrates programmatically setting selectedItem to “Item 2”, which will trigger scrolling to that item.

Remember to choose the appropriate method based on your specific use case. For automatic scrolling with new items, the first approach is suitable. For programmatic scrolling to a specific item, the second approach using ScrollViewReader is more appropriate.