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

What’s so good about Zhang Xiaolong’s big move, WeChat Mini Program?

On December 28, Zhang Xiaolong, the father of WeC...

Congratulations! It’s the first anniversary of Chang’e 5’s launch!

Today is November 24th One year ago today, China&...

Xiaohongshu platform’s distribution strategy and case analysis

According to relevant data, China's cosmetics...

How much does it cost to produce the Huangnan flash sale mini program?

Huangnan seckill applet production price 1. Displ...

How to use coupons? A brief analysis of how to play the activity!

Coupons are rarely missed in any activities among...

How to plan an offline event at zero cost?

Not long ago, on World Intellectual Property Day,...

6 trends of smart hardware in 2015

[[124824]] 2014 is coming to an end, and it is cl...

LeEco co-founder Ding Lei resigns

Ding Lei, co-founder of LeEco Auto, announced on ...

"Facial paralysis caused by air conditioning?" Doctors remind: The truth is...

As summer approaches, the temperature is getting ...

Introduction to Alipay advertising, Alipay information flow advertising agent

Alipay APP, owned by Ant Financial Group, has a l...

Things to note when increasing downloads of startup apps in the Apple Store!

1. Make sure users can find your app! If users kn...

The mobile phone "nuclear war" is not over yet

Once upon a time, when we bought mobile phones an...