【问题标题】:How do I detect when User has reached the bottom of the ScrollView?如何检测用户何时到达 ScrollView 的底部?
【发布时间】:2021-08-06 11:45:43
【问题描述】:

我使用了以下代码作为参考:

我认为它非常接近。 似乎可以通过使用origin.maxY而不是origin.y来解决, 但是origin.maxY似乎没有在GeometryReader中提供(严格来说:CGRect)。

如何检测用户何时到达 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()
  }
}

【问题讨论】:

    标签: ios swift swiftui


    【解决方案1】:

    将整个ScrollView 包裹在ChildSizeReader 中,这样您就可以获得ScrollView 本身的高度。

    因为偏移量从顶部开始为零,所以在滚动视图底部时,结束点不在屏幕顶部,而是在底部。这个差异就是滚动视图的高度。这意味着ScrollView 从偏移量0 开始并转到total content height - scroll view height

    代码:

    struct ContentView: View {
        let spaceName = "scroll"
    
        @State var wholeSize: CGSize = .zero
        @State var scrollViewSize: CGSize = .zero
    
        var body: some View {
            ChildSizeReader(size: $wholeSize) {
                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 - wholeSize.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)
                }
            )
        }
    }
    

    请注意,您已经存在的 scrollViewSize 变量是内容的大小,而不是滚动视图的大小。

    还请注意,我将 == 更改为 &gt;= - 这样您就不必完全处于高度,可以在橡皮筋向后滚动的地方过度滚动。

    【讨论】:

    • @Sajjon 来自他们原来的问题
    【解决方案2】:

    实现这一点的一个非常简单的方法如下:

    struct ContentView: View {
        let array: [String] = (0...100).map { "\($0)" }
        let onEndOfList: () -> Void
        var body: some View {
            List {
                ForEach(array, id: \.self) { element in
                    Text(element)
                }
                Color.clear
                    .frame(width: 0, height: 0, alignment: .bottom)
                    .onAppear {
                        onEndOfList()
                    }
            }
        }
    }
    
    

    不用说,这个基本思想可以被增强并应用于 ScrollView 或任何其他可滚动的 View。

    【讨论】:

    • 这个问题是额外的空列表行,但我想有办法解决这个问题
    • 其实是的,当容器是List的时候。一种可能的解决方案是将不可见视图添加到最后一个元素。
    猜你喜欢
    • 1970-01-01
    • 2019-03-23
    • 1970-01-01
    • 1970-01-01
    • 2015-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多