2

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 :)

aspnet82
  • 33
  • 1
  • 8
  • 0. You did some work before asking a question and this is great. 1. Never post the API keys to open source as you did https://github.com/aspnet82/MovieSearch/blob/master/Discover_Movie/Model/SearchMovieManager.swift 2. The code is very long to study out. Is it possible from you to show some key points, where the things go wrong? 3. I've found some aspects, when you have dataTask - inside of its closure you should have `[weak self]`. 4. Have you tried to put your `let movies = try decoder...` inside the Dispatch async block? – Aleksey Potapov Mar 06 '20 at 12:08
  • Please, check [this example project](https://github.com/alekseypotapov-dev/GithubHeartSwift), as it is very similar to what you're asking. Especially [this view](https://github.com/alekseypotapov-dev/GithubHeartSwift/blob/master/GithubHeartSwift/Views/RepositoryListView.swift) and [its model](https://github.com/alekseypotapov-dev/GithubHeartSwift/blob/master/GithubHeartSwift/ViewModels/RepositoryListViewModel.swift). P.S. This is not self-advertisement, just the issue regarding paging and appending List with data is the popular thing I decided to save somewhere to remember. – Aleksey Potapov Mar 06 '20 at 12:16
  • I tried to put the try decoder inside the Dispatch async block but returns an error. I am also updating the question to give more context – aspnet82 Mar 06 '20 at 13:23
  • if `onTapGesture` you exchange with `onAppear` ? Like: `List { ForEach(your_items) { item in Cell(with: item) } Text("loading...") .onAppear { self.viewModel.loadNextPage() }` – Aleksey Potapov Mar 06 '20 at 15:02
  • I did that, but the issue is that the text appear immediately and results in the array with always the same item. With the tapGesture it works, just is not automatic. Ideally would be nice to evaluate the table scroll and when I arrive at the end it fetches the new data – aspnet82 Mar 06 '20 at 16:33
  • 1
    see https://swiftui-lab.com/a-powerful-combo/ the second example there is exactly what you want. – user3441734 Mar 06 '20 at 17:03
  • see my answer, it use different approach and works with ScrollView or List, whatever you prefer – user3441734 Mar 06 '20 at 20:01

1 Answers1

3

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 ...

enter image description here

user3441734
  • 16,722
  • 2
  • 40
  • 59
  • Tested on iOS 13.6 & 14.5. The code works. Could you please add some comments? I have been able to understand most of the code. – Darkwonder Oct 06 '21 at 06:09
  • @Darkwonder I'm happy to answer a specific question about each line of code in which you have doubts. – user3441734 Nov 02 '21 at 06:29