How to use scroll offset of ScrollView in SwiftUI

How to use scroll offset of ScrollView in SwiftUI

Preface

Now that WWDC 24 is over, I decided to start writing some articles about the new features coming to the SwiftUI framework. This year, Apple continued to fill in the gaps and introduced more fine-grained control over the scroll position. This week, we will learn how to manipulate and read the scroll offset.

Using scrollPosition

The SwiftUI framework already allows us to track and set the position of a scroll view via a view identifier. This approach works well, but is not sufficient to track user interactions more accurately.

 struct ContentView: View { @State private var position: Int? var body: some View { ScrollView { LazyVStack { ForEach(0..<100) { index in Text(verbatim: index.formatted()) .id(index) } } .scrollTargetLayout() } .scrollPosition(id: $position) } }

In the code example above, we used a view identifier and the scrollPosition modifier to track and set the position of the scroll view. While this approach works well, it may not be enough in some cases, especially when more precise user interaction tracking is required. To make up for this, SwiftUI introduces a new ScrollPosition type that allows us to combine scroll positions by offsets, the edges of the scroll view, view identifiers, and more.

New ScrollPosition Type

The SwiftUI framework introduces the new ScrollPosition type, which enables us to combine the scroll position by offset, the edge of the scroll view, the view identifier, etc.

 struct ContentView: View { @State private var position = ScrollPosition(edge: .top) var body: some View { ScrollView { Button("Scroll to bottom") { position.scrollTo(edge: .bottom) } ForEach(1..<100) { index in Text(verbatim: index.formatted()) .id(index) } Button("Scroll to top") { position.scrollTo(edge: .top) } } .scrollPosition($position) } }

As shown in the example above, we define the position state property and bind the scroll view to the state property using the scrollPosition view modifier. We also place two buttons that allow you to quickly scroll to the first or last item in the scroll view. The ScrollPosition type provides many overloaded scrollTo functions that enable us to handle different situations.

Add animation to scroll

We can easily add animation to programmatic scrolling by attaching the animation view modifier and passing an instance of the ScrollPosition type as the value parameter.

 struct ContentView: View { @State private var position = ScrollPosition(edge: .top) var body: some View { ScrollView { Button("Scroll to bottom") { position.scrollTo(edge: .bottom) } ForEach(1..<100) { index in Text(verbatim: index.formatted()) .id(index) } Button("Scroll to top") { position.scrollTo(edge: .top) } } .scrollPosition($position) .animation(.default, value: position) } }

Scroll to a specific item

We added another button to change the position of the scroll view to a random item. We still use the scrollTo function of the ScrollPosition type, but we provide a hashable identifier. This option allows us to change the position to a specific item, and by using the anchor parameter we can choose which point of the selected view should be visible.

 struct ContentView: View { @State private var position = ScrollPosition(edge: .top) var body: some View { ScrollView { Button("Scroll somewhere") { let id = (1..<100).randomElement() ?? 0 position.scrollTo(id: id, anchor: .center) } ForEach(1..<100) { index in Text(verbatim: index.formatted()) .id(index) } } .scrollPosition($position) .animation(.default, value: position) } }

Scroll to a specific offset

Last but not least is the point parameter overload of the scrollTo function, which allows us to pass a CGPoint instance to scroll the view to a specific point in the content.

 struct ContentView: View { @State private var position = ScrollPosition(edge: .top) var body: some View { ScrollView { Button("Scroll to offset") { position.scrollTo(point: CGPoint(x: 0, y: 100)) } ForEach(1..<100) { index in Text(verbatim: index.formatted()) .id(index) } } .scrollPosition($position) .animation(.default, value: position) } }

As shown in the example above, we use the scrollTo function with a CGPoint parameter. It also provides overloads that allow us to scroll the view only on the X or Y axis.

 struct ContentView: View { @State private var position = ScrollPosition(edge: .top) var body: some View { ScrollView { Button("Scroll to offset") { position.scrollTo(y: 100) position.scrollTo(x: 200) } ForEach(1..<100) { index in Text(verbatim: index.formatted()) .id(index) } } .scrollPosition($position) .animation(.default, value: position) } }

Reading the scroll position

We learned how to manipulate the scroll position using the new ScrollPosition type, which also allows us to read the position of the scroll view. ScrollPosition provides optional edge, point, and viewID properties to read values ​​when you scroll programmatically.

Whenever the user interacts with the scroll view, these properties become nil. The isPositionedByUser property on the ScrollPosition type allows us to understand when a user gesture moves the scroll view content.

Here is a runnable example:

Here is a working example code that demonstrates how to read and display the position of a scroll view. We will use a Text view to display the current scroll position:

 import SwiftUI struct ContentView: View { @State private var position = ScrollPosition(edge: .top) @State private var scrollOffset: CGPoint? var body: some View { VStack { ScrollView { LazyVStack { ForEach(0..<100) { index in Text("Item \(index)") .id(index) .padding() .background(Color.yellow) .cornerRadius(10) .padding(.horizontal) } } .scrollPosition($position) .onScrollGeometryChange { geometry in scrollOffset = geometry?.contentBounds.origin } } .animation(.default, value: position) if let offset = scrollOffset { Text("Scroll Offset: x = \(Int(offset.x)), y = \(Int(offset.y))") .padding() } else { Text("Scroll Offset: not available") .padding() } } .padding() } }

In this example, we use the onScrollGeometryChange modifier to read the geometry change of the scroll view. Whenever the scroll view scrolls, geometry?.contentBounds.origin will provide the offset of the current scroll position. We store this offset in the scrollOffset state property and display the current scroll position at the bottom of the view.

Summarize

In this article, we explored the new features of ScrollView in the SwiftUI framework, especially how to achieve more precise scrolling control through the ScrollPosition type. We introduced how to use the ScrollPosition type to set and read the scroll position, including operations such as offsets and view identifiers. In addition, we also showed how to enhance the user experience through animation and event handling. With these new features, developers can more flexibly control the behavior of the scroll view, thereby creating a more fluid and intuitive user interface. I hope this content is helpful to you.

<<:  Detailed explanation of Handler synchronization barrier mechanism (sync barrier) in Android development

>>:  Detailed explanation of Android Native memory leak detection solution

Recommend

LG's first 2K screen mobile phone preview

Yesterday, LG's first 2K screen mobile phone i...

From environment setup to memory analysis: A guide to Python code optimization

Code address: https://github.com/apatrascu/huntin...

Can ordinary people go into space and see the universe?

With a deafening roar, the rocket took off, and t...

Changya APP Competitive Product Analysis Report

Traditional offline KTVs can no longer meet peopl...

How to place, operate and optimize Toutiao ads?

The first step in Toutiao advertising: understand...

GITC 2014: Elites gather to soar to the sky

[[120831]] In recent years, with the support of n...

17 Tik Tok tricks to predict how to make a hit

In fact, this article is about creativity. Some o...

Today’s Toutiao’s WeToutiao traffic generation strategy!

Many people may not know that it was not Edison w...

The Arctic isn't too cold?

Arctic Earth's North The total area exceeds 2...