0

I create a View Controller that contains a UITableView and each UITableViewCell contains a UICollectionView. My problem is that I can't have a different result on every CollectionView. I parse data with JSON. I have a static array but it looks like its empty.

In cellForItemAt, I get an error: Index out of range.

Here is my ViewController

import UIKit

class HomeScreenCategoriesViewController: UIViewController,     UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var tableView: UITableView!
var moviesCategories = ["Popular", "Now Playing", "Latest", "Top Rated", "Upcoming"]
var popularMovies = MovieModel()
var nowPlayingMovies = MovieModel()
static var movies: [MovieModel] = []



override func viewDidLoad() {
    super.viewDidLoad()

    tableView.rowHeight = 130
    tableView.tableFooterView = UIView()
    parsePopularMovies()
    parseNowPlayingMovies()

    DispatchQueue.main.async {
        self.tableView.reloadData()
    }

}

func numberOfSections(in tableView: UITableView) -> Int {
    return 2
}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return "\(moviesCategories[section])"
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 1
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "HomeScreenMovieTableViewCell", for: indexPath) as? HomeScreenCategoriesTableViewCell
    {
        return cell
    }
    return UITableViewCell()
}


func parsePopularMovies() {

    let jsonUrlString = "URLwithMyAPIkey"
    guard let url = URL(string: jsonUrlString) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, err) in

        guard let data = data else { return }

        do {
            let tempMovies = try
                JSONDecoder().decode(MovieModel.self, from: data)

            self.popularMovies.page = tempMovies.page
            self.popularMovies.total_results = tempMovies.total_results
            self.popularMovies.total_pages = tempMovies.total_pages
            self.popularMovies.results = tempMovies.results

            for i in 0..<(self.popularMovies.results?.count ?? 0) {
                let tempPosterPath = "https://image.tmdb.org/t/p/w500" + (self.popularMovies.results?[i].poster_path)!!
                let tempBackDropPath = "https://image.tmdb.org/t/p/w500" + (self.popularMovies.results?[i].backdrop_path)!!
                self.popularMovies.results?[i].poster_path = tempPosterPath
                self.popularMovies.results?[i].backdrop_path = tempBackDropPath
                HomeScreenCategoriesViewController.movies.append(self.popularMovies)
            }

        } catch let jsonErr {
            print("Error serializing json:", jsonErr)
        }
        }.resume()

}


func parseNowPlayingMovies() {

    let jsonUrlString = "URLwithMyAPIkey"
    guard let url = URL(string: jsonUrlString) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, err) in

        guard let data = data else { return }

        do {
            let tempMovies = try
                JSONDecoder().decode(MovieModel.self, from: data)

            self.nowPlayingMovies.page = tempMovies.page
            self.nowPlayingMovies.total_results = tempMovies.total_results
            self.nowPlayingMovies.total_pages = tempMovies.total_pages
            self.nowPlayingMovies.results = tempMovies.results

            for i in 0..<(self.nowPlayingMovies.results?.count ?? 0) {
                let tempPosterPath = "https://image.tmdb.org/t/p/w500" + (self.nowPlayingMovies.results?[i].poster_path)!!
                //let tempBackDropPath = "https://image.tmdb.org/t/p/w500" + (self.nowPlayingMovies.results?[i].backdrop_path)!!
                self.nowPlayingMovies.results?[i].poster_path = tempPosterPath
                HomeScreenCategoriesViewController.movies.append(self.nowPlayingMovies)
            }

        } catch let jsonErr {
            print("Error serializing json:", jsonErr)
        }
        }.resume()

}

}

and here is my TableViewCell

import UIKit

class HomeScreenCategoriesTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

@IBOutlet var collectionView: UICollectionView!
var sectionIndex:Int?
static var movies: [MovieModel] = []

override func awakeFromNib() {

    super.awakeFromNib()
    self.collectionView.delegate = self
    self.collectionView.dataSource = self

    DispatchQueue.main.async {
        self.collectionView.reloadData()
    }

}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "HomeScreenMovieTableViewCell", for: indexPath) as! HomeScreenCategoriesTableViewCell
    cell.sectionIndex = indexPath.section

    return cell
}


func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 1
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 5
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeScreenMovieCell", for: indexPath) as! HomeScreenCategoriesCollectionViewCell

    cell.test.text = HomeScreenCategoriesTableViewCell.movies[collectionView.tag].results?[indexPath.row].title!

    return cell
}

}

My model is this:

struct MovieResults: Codable, Equatable {

let id: Int?
let title: String?
let overview: String?
let adult: Bool?
let original_language: String?
var poster_path: String?
var backdrop_path: String?
let vote_average: Float?
let release_date: String?
}

struct MovieModel: Codable {

var page: Int?
var total_results: Double?
var total_pages: Int?
var results: [MovieResults]?
}

I tried to follow this instruction (How to get section of UITableView from inside a child UICollectionview) but I can't​ find a solution

What am I doing wrong?

Alex Giatrakis
  • 365
  • 3
  • 16

1 Answers1

1

There are few issues here:

  1. Model naming The data model object you use is confusing. MovieModel sounds like it should represent a single movie. But looking at parsing functions,

            self.nowPlayingMovies.page = tempMovies.page
            self.nowPlayingMovies.total_results = tempMovies.total_results
            self.nowPlayingMovies.total_pages = tempMovies.total_pages
            self.nowPlayingMovies.results = tempMovies.results
    

    it looks like there are multiple entries in that object. It should probably be called MovieCategoryModel. HomeScreenCategoriesTableViewCell should have a model that looks like this:

    var movieCategory: MovieCategoryModel!

Since you are going to have different movies in different sections, movieCategory property should not be static.

  1. cellForItemAt indexPath In this method you are trying to configure cell UI with the data about the movie. But the properties of HomeScreenCategoriesTableViewCell was never populated.

  2. numberOfItemsInSection This should return the number of movies in that section. Your code returns 5 - which is some arbitrary number. That's the issue for the error. You should return movieCategory.total_results

  3. cellForRowAt indexPath In HomeScreenCategoriesViewController when you dequeue HomeScreenMovieTableViewCell, you need to pass the movies to that cell, so it will have data to present. You need to do something like:

    if section == 0 { cell.movieCategory = popularMovies } else if section == 1 { cell.movieCategory = nowPlayingMovies }

  4. In general, from the parsing code, you need to save the movies separately for each category. That way in the tableView delegate methods you can easily fetch the data you need for the section.

  5. Parsing code also needs some work, as I can see you are cycling through the items contained within the object

    for i in 0..<(self.nowPlayingMovies.results?.count ?? 0)

but adding the whole object to the array within that same loop

`HomeScreenCategoriesViewController.movies.append(self.nowPlayingMovies)`

Edit based on extra information provided:

MovieResults is very confusing name for an object that represents a single Movie. I would suggest changing it to just Movie. Then MovieModel - the object that contains multiple movies, would be a MovieCategory. Maybe it's also a good idea to store the title of that category within the object itself?

Models

struct Movie: Codable, Equatable {

  let id: Int?
  let title: String?
  let overview: String?
  let adult: Bool?
  let original_language: String?
  var poster_path: String?
  var backdrop_path: String?
  let vote_average: Float?
  let release_date: String?
}

struct MovieCategory: Codable {
  var title: String?
  var page: Int?
  var total_results: Double?
  var total_pages: Int?
  var results: [Movie]?
}

View Controller

import UIKit

class HomeScreenCategoriesViewController: UIViewController,     UITableViewDelegate, UITableViewDataSource {

  @IBOutlet weak var tableView: UITableView!
  var moviesCategories: [MovieCategory] = []


  override func viewDidLoad() {
    super.viewDidLoad()

    tableView.rowHeight = 130
    tableView.tableFooterView = UIView()
    parsePopularMovies()
    parseNowPlayingMovies()

  }

  func numberOfSections(in tableView: UITableView) -> Int {
    return moviesCategories.count
  }

  func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    let category = moviesCategories[section]
    return category.title
  }

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 1
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "HomeScreenMovieTableViewCell", for: indexPath) as? HomeScreenCategoriesTableViewCell
    {
      cell.movieCategory = moviesCategories[indexPath.section]
      return cell
    }
    return UITableViewCell()
  }


  func parsePopularMovies() {

    let jsonUrlString = "URLwithMyAPIkey"
    guard let url = URL(string: jsonUrlString) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, err) in

      guard let data = data else { return }

      do {
        var popularMovies = try
          JSONDecoder().decode(MovieCategory.self, from: data)
        popularMovies.title = "Popular Movies"

        for i in 0..<(popularMovies.results?.count ?? 0) {
          let tempPosterPath = "https://image.tmdb.org/t/p/w500" + (popularMovies.results?[i].poster_path)!!
          let tempBackDropPath = "https://image.tmdb.org/t/p/w500" + (popularMovies.results?[i].backdrop_path)!!
          popularMovies.results?[i].poster_path = tempPosterPath
          popularMovies.results?[i].backdrop_path = tempBackDropPath
        }
        self.moviesCategories.append(popularMovies)
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }

      } catch let jsonErr {
        print("Error serializing json:", jsonErr)
      }
      }.resume()

  }


  func parseNowPlayingMovies() {

    let jsonUrlString = "URLwithMyAPIkey"
    guard let url = URL(string: jsonUrlString) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, err) in

      guard let data = data else { return }

      do {
        var nowPlayingMovies = try
          JSONDecoder().decode(MovieCategory.self, from: data)

        for i in 0..<(nowPlayingMovies.results?.count ?? 0) {
          let tempPosterPath = "https://image.tmdb.org/t/p/w500" + (nowPlayingMovies.results?[i].poster_path)!!
          //let tempBackDropPath = "https://image.tmdb.org/t/p/w500" + (self.nowPlayingMovies.results?[i].backdrop_path)!!
          nowPlayingMovies.results?[i].poster_path = tempPosterPath
        }
        self.moviesCategories.append(nowPlayingMovies)
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
      } catch let jsonErr {
        print("Error serializing json:", jsonErr)
      }
      }.resume()

  }

}

TableViewCell

class HomeScreenCategoriesTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

  @IBOutlet var collectionView: UICollectionView!
  var sectionIndex:Int?
  var movieCategory: MovieCategory!

  override func awakeFromNib() {

    super.awakeFromNib()
    self.collectionView.delegate = self
    self.collectionView.dataSource = self

    DispatchQueue.main.async {
      self.collectionView.reloadData()
    }

  }

  func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 1
  }

  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 5
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeScreenMovieCell", for: indexPath) as! HomeScreenCategoriesCollectionViewCell

    if indexPath.row < movieCategory.results?.count ?? 0 {
      let movie = movieCategory.results?[indexPath.row]
      cell.test.text = movie.title
    }

    return cell
  }

}
Kolineal
  • 465
  • 5
  • 12