问题
I am trying to learn SwiftUI and creating a movie search app with The movie Database API
I would like to fetch new data once the scroll goes at the end of the List. I found a possible solution on SO using ForEach inside the List and checking when the list reaches the last item and then performing the onAppear() call.
In SwiftUI, where are the control events, i.e. scrollViewDidScroll to detect the bottom of list data
How can I load new pages from the search when the first page has loaded?
You can find the project here, was too big to post https://github.com/aspnet82/MovieSearch
SearchMovieManager -> Make the fetch call, I used Dispatch.main.async
Here what the app should do
- Fetch the data from the API and display the first page -> WORKING
- When the List scroll to the last item makes another fetch request on the following page to load more data -> This not works, it works only for the second page of the request. It always displays the same data after the second fetch call.
The issue I think is in the GCD, but I do not know how to queue things to make it works automatically
UPDATE: A workaround I found:
List(searchMovieManager.allMovies) { movie in
Text(movie.title)
}
Text("load more").onTapGesture {
fetchNextPage(obs: self.searchMovieManager, page: self.searchMovieManager.pageTofetch)
}
I think might be ok as solution, it adds new page once I tap on the button, and can be also fine in controlling data download maybe?
Thanks for the help :)
回答1:
Try the next, it use anchor preferences and simple model which mimics async operation to add some records to ScrollView (or List)
import SwiftUI
struct PositionData: Identifiable {
let id: Int
let center: Anchor<CGPoint>
}
struct Positions: PreferenceKey {
static var defaultValue: [PositionData] = []
static func reduce(value: inout [PositionData], nextValue: () -> [PositionData]) {
value.append(contentsOf: nextValue())
}
}
struct Data: Identifiable {
let id: Int
}
class Model: ObservableObject {
var _flag = false
var flag: Bool {
get {
_flag
}
set(newValue) {
if newValue == true {
_flag = newValue
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self._flag = false
self.rows += 20
print("done")
}
}
}
}
@Published var rows = 20
}
struct ContentView: View {
@ObservedObject var model = Model()
var body: some View {
List {
ForEach(0 ..< model.rows, id:\.self) { i in
Text("row \(i)").font(.largeTitle).tag(i)
}
Rectangle().tag(model.rows).frame(height: 0).anchorPreference(key: Positions.self, value: .center) { (anchor) in
[PositionData(id: self.model.rows, center: anchor)]
}.id(model.rows)
}
.backgroundPreferenceValue(Positions.self) { (preferences) in
GeometryReader { proxy in
Rectangle().frame(width: 0, height: 0).position(self.getPosition(proxy: proxy, tag: self.model.rows, preferences: preferences))
}
}
}
func getPosition(proxy: GeometryProxy, tag: Int, preferences: [PositionData])->CGPoint {
let p = preferences.filter({ (p) -> Bool in
p.id == tag
})
if p.isEmpty { return .zero }
if proxy.size.height - proxy[p[0].center].y > 0 && model.flag == false {
self.model.flag.toggle()
print("fetch")
}
return .zero
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and here is how it looks like ...
来源:https://stackoverflow.com/questions/60563668/swiftui-async-data-fetch