在 SwiftUI 中,控制事件在哪里,即 scrollViewDidScroll 检测列表数据的底部
In SwiftUI, where are the control events, i.e. scrollViewDidScroll to detect the bottom of list data
在 SwiftUI 中,有谁知道控制事件(例如 scrollViewDidScroll)在哪里以检测用户何时到达列表底部导致事件检索其他数据块?或者有没有新的方法可以做到这一点?
似乎 UIRefreshControl() 也不存在...
SwiftUI 缺少很多功能 - 目前似乎不可能。
但这里有一个解决方法。
TL;DR skip directly at the bottom of the answer
比较 ScrollView
和 List
时的一个有趣发现:
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(1...100) { item in
Text("\(item)")
}
Rectangle()
.onAppear { print("Reached end of scroll view") }
}
}
}
我在 ScrollView
中的 100 Text
项的末尾附加了 Rectangle
,在 onDidAppear
中附加了 print
。
它在 ScrollView
出现时触发,即使它显示了前 20 个项目。
Scrollview 内的所有视图都会立即呈现,即使它们在屏幕外也是如此。
我对 List
进行了相同的尝试,但行为有所不同。
struct ContentView: View {
var body: some View {
List {
ForEach(1...100) { item in
Text("\(item)")
}
Rectangle()
.onAppear { print("Reached end of scroll view") }
}
}
}
print
仅在到达 List
底部时执行!
所以这是一个临时解决方案,直到 SwiftUI API 变得更好.
Use a List
and place a "fake" view at the end of it, and put fetching logic inside onAppear { }
您可以检查最新的元素是否出现在 onAppear 中。
struct ContentView: View {
@State var items = Array(1...30)
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text("\(item)")
.onAppear {
if let last == self.items.last {
print("last item")
self.items += last+1...last+30
}
}
}
}
}
}
如果您需要有关 scrollView 或列表如何滚动的更精确信息,您可以使用以下扩展作为解决方法:
extension View {
func onFrameChange(_ frameHandler: @escaping (CGRect)->(),
enabled isEnabled: Bool = true) -> some View {
guard isEnabled else { return AnyView(self) }
return AnyView(self.background(GeometryReader { (geometry: GeometryProxy) in
Color.clear.beforeReturn {
frameHandler(geometry.frame(in: .global))
}
}))
}
private func beforeReturn(_ onBeforeReturn: ()->()) -> Self {
onBeforeReturn()
return self
}
}
您可以像这样利用更改后的框架的方式:
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(0..<100) { number in
Text("\(number)").onFrameChange({ (frame) in
print("Origin is now \(frame.origin)")
}, enabled: number == 0)
}
}
}
}
滚动时将调用 onFrameChange
闭包。使用与透明颜色不同的颜色可能会带来更好的性能。
编辑:我通过将框架置于 beforeReturn
闭包之外对代码进行了一些改进。这有助于 geometryProxy 在该闭包中不可用的情况。
我尝试了这个问题的答案,但收到错误 Pattern matching in a condition requires the 'case' keyword
,如 @C.Aglar。
我更改了代码以检查出现的项目是否是列表的最后一项,它将 print/execute 从句。一旦您滚动并到达列表的最后一个元素,此条件就会成立。
struct ContentView: View {
@State var items = Array(1...30)
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text("\(item)")
.onAppear {
if item == self.items.last {
print("last item")
fetchStuff()
}
}
}
}
}
}
OnAppear 解决方法在嵌套在 ScrollView 内部的 LazyVStack 上工作正常,例如:
ScrollView {
LazyVStack (alignment: .leading) {
TextField("comida", text: $controller.searchedText)
switch controller.dataStatus {
case DataRequestStatus.notYetRequested:
typeSomethingView
case DataRequestStatus.done:
bunchOfItems
case DataRequestStatus.waiting:
loadingView
case DataRequestStatus.error:
errorView
}
bottomInvisibleView
.onAppear {
controller.loadNextPage()
}
}
.padding()
}
LazyVStack 是懒惰的,所以只在它快要出现在屏幕上时才创建底部
我在 ScrollView
的视图修饰符中提取了 LazyVStack
加上不可见视图,可以像这样使用:
ScrollView {
Text("Some long long text")
}.onScrolledToBottom {
...
}
实施:
extension ScrollView {
func onScrolledToBottom(perform action: @escaping() -> Void) -> some View {
return ScrollView<LazyVStack> {
LazyVStack {
self.content
Rectangle().size(.zero).onAppear {
action()
}
}
}
}
}
在 SwiftUI 中,有谁知道控制事件(例如 scrollViewDidScroll)在哪里以检测用户何时到达列表底部导致事件检索其他数据块?或者有没有新的方法可以做到这一点?
似乎 UIRefreshControl() 也不存在...
SwiftUI 缺少很多功能 - 目前似乎不可能。
但这里有一个解决方法。
TL;DR skip directly at the bottom of the answer
比较 ScrollView
和 List
时的一个有趣发现:
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(1...100) { item in
Text("\(item)")
}
Rectangle()
.onAppear { print("Reached end of scroll view") }
}
}
}
我在 ScrollView
中的 100 Text
项的末尾附加了 Rectangle
,在 onDidAppear
中附加了 print
。
它在 ScrollView
出现时触发,即使它显示了前 20 个项目。
Scrollview 内的所有视图都会立即呈现,即使它们在屏幕外也是如此。
我对 List
进行了相同的尝试,但行为有所不同。
struct ContentView: View {
var body: some View {
List {
ForEach(1...100) { item in
Text("\(item)")
}
Rectangle()
.onAppear { print("Reached end of scroll view") }
}
}
}
print
仅在到达 List
底部时执行!
所以这是一个临时解决方案,直到 SwiftUI API 变得更好.
Use a
List
and place a "fake" view at the end of it, and put fetching logic insideonAppear { }
您可以检查最新的元素是否出现在 onAppear 中。
struct ContentView: View {
@State var items = Array(1...30)
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text("\(item)")
.onAppear {
if let last == self.items.last {
print("last item")
self.items += last+1...last+30
}
}
}
}
}
}
如果您需要有关 scrollView 或列表如何滚动的更精确信息,您可以使用以下扩展作为解决方法:
extension View {
func onFrameChange(_ frameHandler: @escaping (CGRect)->(),
enabled isEnabled: Bool = true) -> some View {
guard isEnabled else { return AnyView(self) }
return AnyView(self.background(GeometryReader { (geometry: GeometryProxy) in
Color.clear.beforeReturn {
frameHandler(geometry.frame(in: .global))
}
}))
}
private func beforeReturn(_ onBeforeReturn: ()->()) -> Self {
onBeforeReturn()
return self
}
}
您可以像这样利用更改后的框架的方式:
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(0..<100) { number in
Text("\(number)").onFrameChange({ (frame) in
print("Origin is now \(frame.origin)")
}, enabled: number == 0)
}
}
}
}
滚动时将调用 onFrameChange
闭包。使用与透明颜色不同的颜色可能会带来更好的性能。
编辑:我通过将框架置于 beforeReturn
闭包之外对代码进行了一些改进。这有助于 geometryProxy 在该闭包中不可用的情况。
我尝试了这个问题的答案,但收到错误 Pattern matching in a condition requires the 'case' keyword
,如 @C.Aglar。
我更改了代码以检查出现的项目是否是列表的最后一项,它将 print/execute 从句。一旦您滚动并到达列表的最后一个元素,此条件就会成立。
struct ContentView: View {
@State var items = Array(1...30)
var body: some View {
List {
ForEach(items, id: \.self) { item in
Text("\(item)")
.onAppear {
if item == self.items.last {
print("last item")
fetchStuff()
}
}
}
}
}
}
OnAppear 解决方法在嵌套在 ScrollView 内部的 LazyVStack 上工作正常,例如:
ScrollView {
LazyVStack (alignment: .leading) {
TextField("comida", text: $controller.searchedText)
switch controller.dataStatus {
case DataRequestStatus.notYetRequested:
typeSomethingView
case DataRequestStatus.done:
bunchOfItems
case DataRequestStatus.waiting:
loadingView
case DataRequestStatus.error:
errorView
}
bottomInvisibleView
.onAppear {
controller.loadNextPage()
}
}
.padding()
}
LazyVStack 是懒惰的,所以只在它快要出现在屏幕上时才创建底部
我在 ScrollView
的视图修饰符中提取了 LazyVStack
加上不可见视图,可以像这样使用:
ScrollView {
Text("Some long long text")
}.onScrolledToBottom {
...
}
实施:
extension ScrollView {
func onScrolledToBottom(perform action: @escaping() -> Void) -> some View {
return ScrollView<LazyVStack> {
LazyVStack {
self.content
Rectangle().size(.zero).onAppear {
action()
}
}
}
}
}