Skip to main content

How to read the size and position of a scrollview

About 3 minSwiftSwiftUIArticle(s)bloghackingwithswift.comcrashcourseswiftswiftuixcodeappstore

How to read the size and position of a scrollview 관련

SwiftUI by Example

Back to Home

How to read the size and position of a scrollview | SwiftUI by Example

How to read the size and position of a scrollview

Updated for Xcode 16

Improved in iOS 18

SwiftUI’s onScrollGeometryChange() modifier lets us be notified when a scroll view changes its content size (how much content it has), content offset (how far the user has scrolled), and more. The API is a little tricky to understand, so I'll show you an example then explain.

This code shows a scrolling list of rows. There is only 1 row at first, but another row is added every time you press a button:

struct ContentView: View {
    @State private var counter = 1

    var body: some View {
        VStack {
            ScrollView {
                ForEach(0..<counter, id: \.self) { i in
                    Text("Row \(i)")
                }
            }
            .onScrollGeometryChange(for: Double.self) { geo in
                geo.contentSize.height
            } action: { oldValue, newValue in
                print("Height is now \(newValue)")
            }

            Button("Add a row") {
                counter += 1
            }
        }
    }
}

Download this as an Xcode projectopen in new window

The important part is at the end: onScrollGeometryChange(). The parameters I've provided are:

  • Double.self. This means we intend to focus on some kind of Double value. It doesn't say what it means, only that it will be a Double.
  • A trailing closure that accepts a geo parameter. This is a ScrollGeometry object, which is what lets us read the content size, offset, insets, and more. This must return a Double, because that's what we said would happen in the first parameter, and you should send back the value you want to watch.
  • A second trailing closure called action, which is called when the watched value from the previous closure changes.

So, we're saying we want to watch the height of our content size, and whenever it changes print out the new value.

ScrollGeometry has a variety of different values we can read, but be careful: if you watch a value that changes extremely frequently, you're generating a lot of work for SwiftUI and it's going to be pretty CPU-intensive.

For example, you could track the exact scroll offset of your scroll view like this:

struct ContentView: View {
    @State private var yOffset = 0.0

    var body: some View {
        VStack {
            ScrollView {
                ForEach(0..<100, id: \.self) { i in
                    Text("Row \(i)")
                }
            }
            .onScrollGeometryChange(for: Double.self) { geo in
                geo.contentOffset.y
            } action: { oldValue, newValue in
                yOffset = newValue
            }

            Text("Offset: \(yOffset)")
        }
    }
}

Download this as an Xcode projectopen in new window

Important

To be absolutely clear, that action closure will fire a lot when the user scrolls, and updating the UI 120 times a second will really burn through the user's battery. I would strongly recommend against using the above code in a shipping project, but hopefully at least it shows you how this modifier works!

Here are some tips to help you make the most of this API without overloading SwiftUI:

  • Your transform closure will be called extremely frequently, so make it as fast as you can and make its return value change as infrequently as you can – you want to reduce how often your action closure is called.
  • Boiling your transformation down to a Boolean (e.g. "are we at least past Y100" or "has the user moved to a negative scroll position?") is very efficient, because it flips states rarely and therefore calls your action closure rarely too.
  • If your action closure needs to update views, do so very carefully – if you're changing multiple views every time the user scrolls down a single point, you'll easily find your scroll performance is poor.
  • If possible, consider using visualEffect() or scrollTransition() instead.
Similar solutions…
How to detect when the size or position of a view changes | SwiftUI by Example

How to detect when the size or position of a view changes
How to position views in a grid using LazyVGrid and LazyHGrid | SwiftUI by Example

How to position views in a grid using LazyVGrid and LazyHGrid
SwiftUI tips and tricks | SwiftUI by Example

SwiftUI tips and tricks
How to position and style subviews that come from a different view | SwiftUI by Example

How to position and style subviews that come from a different view
All SwiftUI property wrappers explained and compared | SwiftUI by Example

All SwiftUI property wrappers explained and compared

이찬희 (MarkiiimarK)
Never Stop Learning.