I used the following code as a reference:
- SwiftUI - Get size of child?
- SwiftUI - How to get size (height) of ScrollView content
- Get the current scroll position of a SwiftUI ScrollView
I think it's pretty close.
It seems like it could probably be solved by using origin.maxY instead of origin.y,
but origin.maxY doesn't seem to be provided in GeometryReader(strictly speaking: CGRect).
How do I detect when User has reached the bottom of the ScrollView?
import SwiftUI
struct ContentView: View {
  let spaceName = "scroll"
  @State var scrollViewSize: CGSize = .zero
  var body: some View {
    ScrollView {
      ChildSizeReader(size: $scrollViewSize) {
        VStack {
          ForEach(0..<100) { i in
            Text("\(i)")
          }
        }
        .background(
          GeometryReader { proxy in
            Color.clear.preference(
              key: ViewOffsetKey.self,
              value: -1 * proxy.frame(in: .named(spaceName)).origin.y
            )
          }
        )
        .onPreferenceChange(
          ViewOffsetKey.self,
          perform: { value in
            print("offset: \(value)") // offset: 1270.3333333333333 when User has reached the bottom
            print("height: \(scrollViewSize.height)") // height: 2033.3333333333333
            if value == scrollViewSize.height {
              print("User has reached the bottom of the ScrollView.")
            } else {
              print("not reached.")
            }
          }
        )
      }
    }
    .coordinateSpace(name: spaceName)
    .onChange(
      of: scrollViewSize,
      perform: { value in
        print(value)
      }
    )
  }
}
struct ViewOffsetKey: PreferenceKey {
  typealias Value = CGFloat
  static var defaultValue = CGFloat.zero
  static func reduce(value: inout Value, nextValue: () -> Value) {
    value += nextValue()
  }
}
struct ChildSizeReader<Content: View>: View {
  @Binding var size: CGSize
  let content: () -> Content
  var body: some View {
    ZStack {
      content().background(
        GeometryReader { proxy in
          Color.clear.preference(
            key: SizePreferenceKey.self,
            value: proxy.size
          )
        }
      )
    }
    .onPreferenceChange(SizePreferenceKey.self) { preferences in
      self.size = preferences
    }
  }
}
struct SizePreferenceKey: PreferenceKey {
  typealias Value = CGSize
  static var defaultValue: Value = .zero
  static func reduce(value _: inout Value, nextValue: () -> Value) {
    _ = nextValue()
  }
}
 
     
     
     
     
    