SwiftUI async Data fetch

南楼画角 提交于 2020-03-25 17:53:00

问题


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

  1. Fetch the data from the API and display the first page -> WORKING
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!