It’s sometimes necessary to make a scroll view (or a list) scroll programmatically. As of iOS 18, SwiftUI offers the scrollPosition(_:) view modifier, which, along with one or two other modifiers, enable programmatic scrolling within just a few easy steps. Using it, scrolling can be done to a particular item, an edge, or just an arbitrary offset to the horizontal or vertical axis. Let’s get to know it in the next few parts.
☝️ Note
To support system versions prior to 18, we still can use the ScrollViewReader container view, which is available as of iOS 14.
The scrollPosition modifier
To get started, suppose we have the following array that stores random integer numbers:
|
1 2 3 |
@State private var randomNumbers = [Int]() |
A few random numbers are generated and assigned to that array on appear of the view, while they are displayed using a scroll view and a ForEach container as shown next; note that there’s a toolbar that remains empty at the time:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
NavigationStack { ScrollView { ForEach(randomNumbers.indices, id: \.self) { index in VStack { HStack { Text(“\(randomNumbers[index])“) Spacer() } Divider() } .padding(.leading) .frame(height: 60) } } .onAppear { for _ in 0..<20 { randomNumbers.append(Int.random(in: 0…1000)) } } .toolbar { ToolbarItemGroup(placement: .primaryAction) { } } } |
There are two distinct steps to make now. The first is to declare a state property that stores the scroll position of the scroll view. The data type of this property is ScrollPosition, and we have to provide an initial value. This can be any of the following:
- The type of the elements presented in the scroll view, i.e., .
init(idType: Int.self)here. - The edge (top, bottom, leading, trailing) that the scroll view should automatically scroll to on appear, i.e.,
.init(edge: .top). - An offset to the horizontal or vertical axis, i.e.,
.init(x: 100).
Unless there are specific requirements, the second way is the easiest one to initialize a ScrollPosition property:
|
1 2 3 |
@State private var position: ScrollPosition = .init(edge: .top) |
With that in place, the second step is to apply the scrollPosition(_:) modifier to the scroll view:
|
1 2 3 4 5 6 7 |
ScrollView { … } .scrollPosition($position) // … other modifiers … |
See that we provide the binding value of the position state property as argument to scrollPosition(_:). The scroll view updates it when scrolling happens programmatically, but the same property gets updated when scrolling is happening manually too.
Scrolling to a specific item
In order to make it possible to scroll to a specific item or row, it’s necessary to uniquely identify them first. We manage that with the id(_:) modifier, providing a unique value for each item.
In this demonstration here, we’ll use the index of each random number in the array, as it’s different for every element. We apply the modifier after the view that presents an item or a row content:
|
1 2 3 4 5 6 7 8 9 |
ForEach(randomNumbers.indices, id: \.self) { index in VStack { … } .id(index) // … other modifiers … } |
We can now go ahead and scroll to any item with a particular identifier using the scrollTo(id:) method, that’s accessible through the position property we declared previously. To make the example here more interesting, we’ll add a button that:
- Insert a new random number to the
randomNumbersarray. - Scrolls to the index of that new item.
In the -still empty- toolbar and ToolbarItemGroup container, we will add a button as the following code shows:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
.toolbar { ToolbarItemGroup(placement: .primaryAction) { Button(“”, systemImage: “plus.circle”) { randomNumbers.append(Int.random(in: 0…1000)) withAnimation { position.scrollTo(id: randomNumbers.count – 1) } } } } |
Notice the scrollTo(id:) method, and the fact that is included in an animation block. The animation here results to a smooth scrolling to the newly added item.

Similarly, we could scroll to any item with a specific id. For instance:
|
1 2 3 |
position.scrollTo(id: 15) |
Scrolling to an edge
In a pretty much same fashion we can scroll programmatically to an edge; top, bottom, leading or trailing. The method to use is different in this case, which is the scrollTo(edge:). It’s important to note that we don’t need to use the id(_:) modifier at all.
Let’s add another button to the toolbar to demonstrate scrolling to edge:
|
1 2 3 4 5 |
Button(“”, systemImage: “arrowshape.up.circle”) { position.scrollTo(edge: .top) } |

Scrolling to a specific point
Similarly as above, we can also scroll to a point either to the horizontal or the vertical axis, depending on the scrolling direction. The following example makes the scroll view scroll automatically in the vertical axis to a specific point:
|
1 2 3 4 5 |
Button(“”, systemImage: “numbers.rectangle”) { position.scrollTo(y: 180) } |

Wrapping up
Scrolling programmatically is a straightforward task with the scrollPosition(_:) modifier and the various ScrollPosition methods we met previously. Unlike the ScrollViewReader which is a container view and everything has to be done in its body, scrollPosition(_:) can be used view-wide, and that offers better flexibility. Just keep in mind that it’s available in iOS 18 and above. Thanks for reading!