13

I want to determine the height of the content inside my (vertical) ScrollView.

ScrollView {  
    //My Content
}

Is this possible? I've tried a few things but nothing seems to work.

Unfortunately, using simply the GeometryReader isn't working, since it returns a value of 10 no matter what content is inside the ScrollView

Thanks!

1
  • 1
    You can consider this my solution for List. It can be adapted for dynamic ScrollView as well. Commented May 28, 2020 at 11:12

3 Answers 3

35

The solution would be to have GeometryReader inside a .background/.overlay modifier. This makes it possible to read the height of the ScrollView content.

struct ContentView: View {

    var body: some View {
        ScrollView {
            ForEach(0..<1000) { i in
                Text("\(i)")
            }
            .background(
                GeometryReader { proxy in
                    Color.clear.onAppear { print(proxy.size.height) }
                }
            )
        }
    }
}

We get an output of 20500.0 which is correct.

Sign up to request clarification or add additional context in comments.

4 Comments

Surprisingly, this also works with LazyVStack with reasonable performance!
For dynamic content in a scrollView, I change the onAppear to onChange and pass it the state that is dynamic, E.g. Color.clear.onChange(of: chatData.messages) { print(proxy.size.height) }
Doesn't work on actual device with LazyVStack
Doesn't work for me!
4

I found another solution to this. You can use a PreferenceKey to propagate the size of the child view up to the parent. Check out this amazing article that goes over how to do this: https://zalogatomek.medium.com/swiftui-missing-intrinsic-content-size-how-to-get-it-6eca8178a71f

It's very similar to your solution with the clear color + GeometryReader, but obfuscates it into a nice view modifier, and sends the @State to the parent view.


struct ContentView: View {
    @State var textSize: CGSize = .zero
    
    var body: some View {
        ScrollView {
            Text("Hi, I'm a text")
                .readIntrinsicContentSize(to: $textSize)
                .background(Color.red)
        
            Text("Size of above is: \(textSize.width) x \(textSize.height)")
                .background(Color.blue)
        }
    }
}

extension View {
    func readIntrinsicContentSize(to size: Binding<CGSize>) -> some View {
        background(GeometryReader { proxy in
            Color.clear.preference(
                key: IntrinsicContentSizePreferenceKey.self,
                value: proxy.size
            )
        })
        .onPreferenceChange(IntrinsicContentSizePreferenceKey.self) {
            size.wrappedValue = $0
        }
    }
}

struct IntrinsicContentSizePreferenceKey: PreferenceKey {
    static let defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}


Comments

2

I was working on pagination and needed a few values from the ScrollView. I came across this question, which contains an answer, but it also includes a few other bonus tips. Keep in mind that this is available on iOS 18.0 and above.

Hopefully someone could use this as a foundation and make it better:

ScrollView {
    // Your content
}
.onScrollGeometryChange(for: [Double].self, of: { geometry in
    [
        geometry.contentSize.height,
        geometry.contentOffset.y,
        geometry.bounds.height
    ]
}, action: { _, newValue in
    let contentHeight = newValue[0]
    let offsetY = newValue[1]
    let scrollViewHeight = newValue[2]

    // print(contentHeight, offsetY, scrollViewHeight)
    // print(vm.currentOffset)

    if offsetY > contentHeight - scrollViewHeight {
        // method for pagination
    }
})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.