最近开发的项目中遇到一个需求,要在ScrollView滑动到底部的时候显示一个loading,然后请求第二页数据。这就涉及到怎么判断ScrollView滑动到底部了。
那么可以用这面这个条件判断:
偏移量 >= (ScrollView内容高度 - ScrollView自身高度)
在用这个判断条件之前,得先获得到ScrollView内容高度和ScrollView自身高度,先介绍个小组件:ChildSizeReader
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
}
}
}
private struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value _: inout Value, nextValue: () -> Value) {
_ = nextValue()
}
}
ChildSizeReader组件接受两个值,size和content,这里的size用了@Binding修饰,需要调用的地方传个引用过来。content就不用多说了。举个例子用一下:
struct ContentView: View {
@State var scrollViewSize: CGSize = .zero
@State var scrollViewContentSize: CGSize = .zero
var body: some View {
VStack {
ChildSizeReader(size: $scrollViewSize) {
ScrollView(.vertical, showsIndicators: false) {
ChildSizeReader(size: $scrollViewContentSize) {
VStack {
ForEach(0..<30, id: \.self) { index in
VStack {
Text("This is the \(index)th line.")
.padding()
.frame(height: 30)
.frame(maxWidth: .infinity, alignment: .leading)
Color.gray
.frame(height: 1)
}
}
}
.onAppear {
print("scrollViewSize: \(scrollViewSize)")
print("scrollViewContentSize: \(scrollViewContentSize)")
}
}
}
}
}
}
}
上面代码中首先定义了两个属性,scrollViewSize和scrollViewContentSize,分别用来记录ScrollView的自身尺寸和ScrollView的内容尺寸。
第一个ChildSizeReader包裹ScrollView,从而计算ScrollView的自身尺寸。
第二个ChildSizeReader包裹了一个VStack,里面进行了遍历加载视图,这样就计算了ScrollView的内容尺寸。
获得了两个尺寸,现在还差一个滑动的偏移量了,这里打算用PreferenceKey来进行记录。关于PreferenceKey的使用介绍,后期的文章会介绍。
private struct ScrollOffsetPreferenceKey: PreferenceKey {
static var defaultValue = CGFloat.zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
现在将上面的demo改一下:
struct ContentView: View {
@State var scrollViewSize: CGSize = .zero
@State var scrollViewContentSize: CGSize = .zero
@Namespace var scrollViewSpace
var body: some View {
VStack {
ChildSizeReader(size: $scrollViewSize) {
ScrollView(.vertical, showsIndicators: false) {
ChildSizeReader(size: $scrollViewContentSize) {
VStack {
ForEach(0..<30, id: \.self) { index in
......
}
}
.background(GeometryReader {
Color.clear.preference(key: ScrollOffsetPreferenceKey.self, value: -$0.frame(in: .named(scrollViewSpace)).minY)
})
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
if value >= scrollViewContentSize.height - scrollViewSize.height {
print("reached bottom.")
}
}
}
}
.coordinateSpace(name: scrollViewSpace)
}
}
}
}
上面代码中首先添加了一个属性:@Namespace var scrollViewSpace,用来标识ScrollView的空间坐标。
再给VStack添加一个background,里面向ScrollOffsetPreferenceKey进行传值。在ScrollView不断滚动的时候,ScrollOffsetPreferenceKey里的reduce函数不断进行计算偏移量。
最后添加一个onPreferenceChange,得到ScrollOffsetPreferenceKey的值,这样通过文章开头说的公式计算一下,就知道什么时候滑动到底了。
别忘了还要给ScrollView添加一个coordinateSpace哦,因为我们要计算基于ScrollView坐标的偏移量。
文章比较简单,介绍了一种判断ScrollView滑动到底部的方法,当然还有其他方法,比如用LazyVStack组件等。