0

First of all please forgive any wrong terminology, I started to learn the escaping closures just a couple of weeks ago.

I have an API "returning" an array in an escaping closure. The function is called like this:

getAllUserMovies(username: user) { (result) in
            switch result {
            case .success(let movies):
                // movies is an array. Do something with each element
                break
            case .error(let error):
                // report error
                break
            }
}

And I need to use the elements of that array, only one per time, in this collection view method (it's actually more complex than this, since I interface with the TMDB API as well):

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell

I tried to use the nested closures, in a similar fashion:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

            let cell : collectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: self.reuseIdentifierLargeBanners, for: indexPath as IndexPath) as! CollectionViewCell

            getAllUserMovies(username: user){ (result) in
                switch result {
                case .success(let movies):
                    let movie = movies[indexPath.row] {
                        cell.imageView.image = movie.poster
                    }
                    break
                case .error(let error):
                    print(error?.localizedDescription ?? "")
                    break
                }
            }
            return cell
        }
        return UICollectionViewCell()
    }

Basically I get two issues. First of all I consider this completely inefficient (I have to fetch the full movie list anytime I refresh a cell) and then I also get funny results, like duplicated banners, which keep refreshing, moving around or missing icons. I implemented the accepted answer of this question, but still I am unable to make it working. Whatever I do I either get duplicated images, empty cell or both.

UPDATE: It appears the missing icons were due to a limit in the number of calls per second in the API. Above that number the API fails and I didn't check the error.

I suppose a possible solution would be to store the "movies" array somewhere, and then be able to fetch the singular movies from it from the collection view method. Refreshing it if/when needed. That now has been fixed, thanks to anuraj answer!

Michele Dall'Agata
  • 1,474
  • 2
  • 15
  • 25
  • 1
    The repeating issue is because cells are reused. And you don't do `cell.imageView.image = nil` (in case of error, or during the loading) to remove the potential previous image. Also, just use a `var movies: [MovieClass]`, and call `getAllUserMovies{}` at some point (init ?) and once you get ti, set the movies and reload the tableView. – Larme Jul 25 '18 at 08:52
  • I have read in other questions people suggesting to reload the tableView. But I don't have any tableView. Is there a reload() for UICollectionView as well? I checked before but I couldn't find how to implement it. – Michele Dall'Agata Jul 25 '18 at 08:58
  • I meant UICollectionView. It’s the same behavior – Larme Jul 25 '18 at 08:59
  • All right then. I'll see where I can go from here. Thanks. – Michele Dall'Agata Jul 25 '18 at 09:00

1 Answers1

1

If you implement API call in cellForItemAt every time when you scroll will result in API calling.

I would suggest you to make a API call in didLoad or willAppear and refresh the collection view after keeping the result globally.

func makeAPICall() {
getAllUserMovies(username: user){ (result) in
                switch result {
                case .success(let movies):
                    self.movies = movies
                    yourCollectionView.reloadData()
                    break
                case .error(let error):
                    print(error?.localizedDescription ?? "")
                    break
                }
            }
}

Collection view DataSource

 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.movies.count
    }
Anuraj
  • 1,242
  • 10
  • 25
  • That's what I was testing (without defining a function), I had it already in didLoad, added reloadData(), but then didLoad gets executed after func collectionView()?? Which gives a runtime error "Thread 1: Fatal error: Index out of range" on "let movieID = self.movies[indexPath.row].movie?.ids.tmdb" – Michele Dall'Agata Jul 25 '18 at 09:31
  • What have you returned for numberOfRow delegate? – Anuraj Jul 25 '18 at 09:36
  • I don't have any. I took a sample from the internet and used it as a base. It worked with local images. Then I added the frameworks, with escaping closures and all and my nightmare started. Anyway, didLoad() (obviously) gets called before anything, but collectionView() gets to access the array before getAllUserMovies returns its values. Sorry for the mistake. – Michele Dall'Agata Jul 25 '18 at 09:41
  • Unless you meant numberOfItemsInSection, that returns 15. I scroll the collection view horizontally (It's tvOS actually) and only one cell is defined in IB (also it works fine with local images). – Michele Dall'Agata Jul 25 '18 at 09:53
  • Since you have returned 15. If the count of movies array is less than 15 it will definitely crash. – Anuraj Jul 25 '18 at 09:56
  • 1
    In numberOfItemsInSection you should return movies.count – Anuraj Jul 25 '18 at 09:57
  • 1
    OMG!! That's SO beautiful! It works, now!! Ok, I get 765 cells, but who's counting? ^_^ I'll put an if. Also my two other collection views disappeared, but I think I can easily move on from here. Thanks!!! – Michele Dall'Agata Jul 25 '18 at 10:00