Search code examples
iosjsonswiftuitableviewdecodable

Parse JSON Using Decodable into TableView Sections with an Array inside an Array


I am attempting to parse data from an api into a tableView with Sections. The end result would be a section title that corresponds with a month and rows with lists of videos that were posted for the month. The videos may not include a poster or description.

I tried to implement a for in loop function to update the model after retrieving the data, which worked great until I started trying to implement sections. I can print the json response to the console and receive the full response.

Here is a sample of the original JSON Structure:

{
    "page": {
        "type": "videos",
        "sections": [{
            "title": "September",
            "videos": [{
                "title": "Some Video",
                "description": "Video Description",
                "poster": "",
                "url": "url"
            }]
        }, {
            "title": "August 2019",
            "videos": [{
                "title": "Some Video",
                "description": "",
                "poster": "Some Image",
                "url": "url"
            }, {
                "title": "Some Video",
                "description": "No Description",
                "poster"",
                "url": "url"
            }]
        }]
    }
}

Here is my Model:

struct Root: Decodable {
    let page: Page  
}

struct Page: Decodable {
    let type: String
    let sections: [VideoSection]
}

struct VideoSection: Decodable {
    let title: String
    let videos: [Video]
}

struct Video: Decodable {
    let videoTitle: String
    let videoDescription: String?
    let poster: String?
    let url: String

    enum CodingKeys: String, CodingKey {
        case videoTitle = "title"
        case videoDescription = "description"
        case poster = "poster"
        case url = "url"
    }
}

Here is may Networking call with Parsing:

func getVideoData(url: String){

    guard let videoUrl = URL(string: "Video_URL") else { return }
    URLSession.shared.dataTask(with: videoUrl) { (data, response, error) in
        guard let data = data else { return }
        do {
            let decoder = JSONDecoder()
            let responseData = try decoder.decode(Root.self, from: data)

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

        } catch let err {
            print("Error", err)
        }
    }.resume()
}

Here is my tableView:

var allvideosArray = [Video]()
var allSectionsArray = [VideoSection]()
var rootArray: Root?

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

    let cell = tableView.dequeueReusableCell(withIdentifier: "customVideoCell", for: indexPath) as! CustomVideoCell

    cell.videoDescriptionPlaceholder.text = Video.CodingKeys.videoDescription.rawValue
    cell.videoTitlePlaceholder.text = Video.CodingKeys.videoTitle.rawValue
    return cell
}

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

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

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

When I attempt to print(responseData.page.sections.videos) I receive the error "Value of type'[VideoSection]' has no member 'videos,' which leads me to believe the issue has to do with the [videos] array inside of the [sections] array.


Solution

  • You can try

    var page:Page?
    

    let responseData = try decoder.decode(Root.self, from: data)
    self.page = responseData.page 
    DispatchQueue.main.async { 
        self.videoTableView.reloadData()
    } 
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
        let cell = tableView.dequeueReusableCell(withIdentifier: "customVideoCell", for: indexPath) as! CustomVideoCell
        let item = page!.sections[indexPath.section].videos[indexPath.row]
        cell.videoDescriptionPlaceholder.text = item.videoDescription 
        cell.videoTitlePlaceholder.text = item.videoTitle
        return cell
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return page?.sections[section].title
    } 
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return page?.sections.count ?? 0
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return page?.sections[section].videos.count ?? 0
    
    }