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

Jinsheng Insurance: How to determine SEO keywords for a new website?

After a website is built, good keywords are very ...

What are the operators doing before the APP is launched?

Nowadays, people tend to divide product operation...

What is SEO keyword domination? How effective is keyword dominance?

Recently, many friends have asked: What does keyw...

How to quickly attract fans through local WeChat public account promotion?

We know that having a local WeChat public account...

Changhong CHiQ TV 40Q1N review: A new way to play with young smart 4K

As emerging enterprises are rising and home applia...

What technologies does Alibaba have in its Flutter system construction?

[[321430]] 2019 is undoubtedly a year of rapid de...

How to formulate SEM delivery strategy? 4 steps to teach you how to complete

How to formulate SEM delivery strategy ? 4 steps ...

5 links to sort out the B-side product operation framework

Currently, many companies that develop B-side pro...