在 SwiftUI 中,控制事件在哪里,即 scrollViewDidScroll 用于检测列表底部数据

In SwiftUI, where are the control events, i.e. scrollViewDidScroll to detect the bottom of list data

提问人:J. Edgell 提问时间:6/15/2019 最后编辑:Matteo PaciniJ. Edgell 更新时间:5/4/2022 访问量:17211

问:

在 SwiftUI 中,有谁知道控制事件(如 scrollViewDidScroll)在哪里,以检测用户何时到达列表底部,从而导致事件检索其他数据块?还是有新的方法可以做到这一点?

似乎 UIRefreshControl() 也不存在......

迅速 SWIFTUI的

评论


答:

29赞 Matteo Pacini 6/15/2019 #1

SwiftUI 缺少很多功能——目前似乎是不可能的

但这里有一个解决方法

TL;DR直接在答案底部跳过

一个有趣的发现,同时对 和 进行了一些比较:ScrollViewList

struct ContentView: View {

    var body: some View {

        ScrollView {
            ForEach(1...100) { item in
                Text("\(item)")
            }
            Rectangle()
                .onAppear { print("Reached end of scroll view")  }
        }
    }

}

我在 a 内的 100 个项目的末尾附加了 a,并在 中附加了 a。RectangleTextScrollViewprintonDidAppear

当它出现时,它就会触发,即使它显示了前 20 个项目。ScrollView

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")  }
        }
    }

}

只有当到达底部时才会执行!printList

因此,这是一个临时解决方案,直到 SwiftUI API 变得更好

使用一个并在其末尾放置一个“假”视图,并在其中放置获取逻辑ListonAppear { }

评论

1赞 J. Edgell 6/15/2019
非常感谢!这是一个我没有想到的绝妙解决方案。希望我能给你不止一个“回答”!
2赞 Rob 6/15/2019
“这可能意味着 Scrollview 中的所有视图都会立即呈现”......使用视图调试器,你会看到这正是正在发生的事情。这是相当标准的滚动视图模式,您可以在其中添加所有内容(然后它可以从中确认并显示适当的滚动指示器)。contentSize
0赞 Dimillian 6/20/2019
这太聪明了。SwiftUI 真的让你与众不同
1赞 MoFlo 6/20/2019
您可以向 a 添加手势,并在拖动时触发。例如,这在这里可能很有用。ScrollView.gesture(DragGesture(minimumDistance: length).onEnded{ _ in print"Done!" })
0赞 ConfusionTowers 10/24/2019
@MoFlo 我用列表尝试了您的建议,当我上下滚动列表时,事件似乎从未触发。猜测 List 结构会以某种方式抑制此类事件。有没有办法看到这个事件?
14赞 Victor Kushnerov 8/19/2019 #2

您可以检查最新的元素是否出现在 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
                    }
                }
            }
        }
    }
}

评论

0赞 C.Aglar 4/29/2020
我收到以下错误消息:“条件中的模式匹配需要'case'关键字” 有什么解决方案吗?
0赞 Peter Schorn 5/26/2020
@C.Aglar,你需要发布你的代码,以便我任何人都知道你为什么会遇到这个错误。
9赞 Menno 11/15/2019 #3

如果您需要有关如何滚动 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

编辑:我通过将框架放在闭包之外来改进代码。这在 geometryProxy 在该闭包中不可用的情况下会有所帮助。beforeReturn

评论

0赞 qwerty-reloader 12/8/2022
非常适合用作 UIKit 委托方法 scrollViewDidScroll 的类似物。
3赞 Yisselda 5/30/2020 #4

我尝试了这个问题的答案,但出现类似 @C.Aglar 的错误。Pattern matching in a condition requires the 'case' keyword

我更改了代码以检查出现的项目是否是列表的最后一个,它将打印/执行子句。滚动并到达列表的最后一个元素后,此条件将为真。

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()
                    }
                }
            }
        }
    }
}
2赞 lipemesq 11/24/2020 #5

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 是懒惰的,所以只有在它几乎出现在屏幕上时才会创建底部

2赞 kanobius 5/4/2022 #6

我已经在视图修饰符中提取了加号不可见视图,可以像这样使用:LazyVStackScrollView

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()
                }
            }
        }
    }
}