mrrobot1999
mrrobot1999

Reputation: 11

JSON isn't loading in the Table view

This is my view controller with Table views

class HomeVC: UIViewController, UITableViewDelegate, UITableViewDataSource {    

    private let myArray: NSArray = ["First", "Second", "Third"]
    private var myTableView: UITableView!

    var articles: [ArticleClass]? = []


    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = UIColor.white
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationItem.title = "Home"

        getData()

        let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
        let displayWidth: CGFloat = self.view.frame.width
        let displayHeight: CGFloat = self.view.frame.height

        myTableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
        myTableView.dataSource = self
        myTableView.delegate = self
        myTableView.register(ArticleCell.self, forCellReuseIdentifier: "MyCell")

        view.addSubview(myTableView)
        myTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        myTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        myTableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
        myTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

    }


    func getData() {

        let theURL = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=34e81be767734526b224ac353b1378e8")
        let task = URLSession.shared.dataTask(with: theURL!) { (data, response, error) in

            if error != nil {

                print(error)
                return

            } else {

                self.articles = [ArticleClass]()

                do {

                    let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]

                    if let articlesFromJSON = json["Articles"] as? [[String: Any]] {

                        for articleOutOfJSON in articlesFromJSON {

                            let theArticle = ArticleClass()

                            if let title = articleOutOfJSON as? String, let author = articleOutOfJSON["author"] as? String, let desc = articleOutOfJSON["description"] as? String, let url = articleOutOfJSON["url"] as? String, let imageToURL = articleOutOfJSON["imageToURL"] as? String {

                                theArticle.theDescription = desc
                                theArticle.author = author
                                theArticle.imageURL = imageToURL
                                theArticle.url = url

                            }

                            //Putting the articleOutOfJSON into our array.
                            self.articles?.append(theArticle)

                        }

                    }

                    //Making the data be on the main thread.
                    DispatchQueue.main.async {

                        self.myTableView.reloadData()

                    }


                } catch {

                    print(error)

                }

            }

        }

        task.resume()

    }




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

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

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

        cell.title.text = self.articles?[indexPath.item].headline
        cell.theDesc.text = self.articles?[indexPath.item].theDescription
        cell.author.text = self.articles?[indexPath.item].author
        cell.theImageView.downloadImage(from: (self.articles?[indexPath.item].url)!)

        return cell

    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return self.articles?.count ?? 0
    }


}

This is my table view cell.

class ArticleCell: UITableViewCell {

    let title: UILabel = {
        let label = UILabel()
        label.text = "Title"
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    let theDesc: UILabel = {
        let label = UILabel()
        label.text = "TEXT TEXT TEXT TEXT TEXT TEXT"
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    let author: UILabel = {
        let label = UILabel()
        label.text = "Author"
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    let theImageView: UIImageView = {
        let image = UIImageView()
        image.backgroundColor = UIColor.purple
        image.translatesAutoresizingMaskIntoConstraints = false
        return image
    }()

    override func awakeFromNib() {
        super.awakeFromNib()

        contentView.addSubview(theImageView)
        theImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 8).isActive = true
        theImageView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -146).isActive = true
        theImageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 30).isActive = true
        theImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -26).isActive = true

        contentView.addSubview(title)
        contentView.addSubview(theDesc)
        contentView.addSubview(author)

    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

This is my data model.

class ArticleClass: NSObject {
    var headline: String?
    var theDescription: String?
    var author: String?
    var url: String?
    var imageURL: String?
    var publishingDate: Int?
}

I have tried loading JSON into my table view and it isn't working. I don't see what is wrong with my code at the moment. If you can help then I would appreciate it. I have looked online for help as well but couldn't manage to get any help based on the problem that I have.

Upvotes: 0

Views: 84

Answers (3)

vadian
vadian

Reputation: 285290

I recommend to use the Decodable protocol to parse the JSON.

First of all you don't need a class inherited from NSObject, a struct is sufficient. The article struct uses the JSON keys as properties:

struct News : Decodable {
    let status : String
    let articles : [Article]
}

struct Article : Decodable {
    let description: String?
    let author: String?
    let url: URL
    let urlToImage: URL?
    let publishedAt: Date
}

Then declare the data source array as non-optional

var articles = [Article]()

Then parse the JSON

func getData() {
    let theURL = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=••••••••••••••••••••")
    let task = URLSession.shared.dataTask(with: theURL!) { (data, response, error) in

        if error != nil { 
           print(error!)
           return 
        } else {
           do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .iso8601
                let news = try decoder.decode(News.self, from: data!)
                self.articles = news.articles

                //Making the data be on the main thread.
                DispatchQueue.main.async {
                    self.myTableView.reloadData()
                }
            } catch {
                print(error)
            }         
        }
    }
    task.resume()
}

There are other issues in your code:

You are mixing up numberOfSections and numberOfRowsInSection. In numberOfSections return 1 or omit the method

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

In numberOfRowsInSection return the number of articles

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

In cellForRow use .row rather than .item

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

    let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! ArticleCell
    let article = self.articles[indexPath.row]
    cell.title.text = article.title
    cell.theDesc.text = article.description
    cell.author.text = article.author
    cell.theImageView.downloadImage(from: article.url)
    return cell
}

PS:

You are strongly discouraged from sharing your real API key in a public forum. A better way is to post the JSON and garble the key.

Upvotes: 3

Dharmesh Kheni
Dharmesh Kheni

Reputation: 71862

As suggested in Husyn's answer you need to replace

if let articlesFromJSON = json["Articles"] as? [[String: Any]] {

with

if let articlesFromJSON = json["articles"] as? [[String: Any]] {

because your Articles key isn't matching with data coming from server and there is one more key which isn't matching is imageToURL which needs to replace with urlToImage as per you data coming from server and your final code will be:

let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]

                if let articlesFromJSON = json["articles"] as? [[String: Any]] {
                    for articleOutOfJSON in articlesFromJSON {

                        let theArticle = ArticleClass()

                        if let title = articleOutOfJSON["title"] as? String, let author = articleOutOfJSON["author"] as? String, let desc = articleOutOfJSON["description"] as? String, let url = articleOutOfJSON["url"] as? String, let imageToURL = articleOutOfJSON["urlToImage"] as? String {

                            theArticle.theDescription = desc
                            theArticle.author = author
                            theArticle.imageURL = imageToURL
                            theArticle.url = url

                        }
                        self.articles?.append(theArticle)
                    }

                }

And if server will send null value then that entire object will not append into your articles object. So for better way check below code:

let theArticle = ArticleClass()
theArticle.theDescription = articleOutOfJSON["description"] as? String ?? ""
theArticle.author = articleOutOfJSON["author"] as? String ?? ""
theArticle.imageURL = articleOutOfJSON["urlToImage"] as? String ?? ""
theArticle.url = articleOutOfJSON["url"] as? String ?? ""
self.articles?.append(theArticle)

And numberOfRowsInSection should be:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.articles?.count ?? 0
}

And remove:

func numberOfSections(in tableView: UITableView) -> Int {
    return self.articles?.count ?? 0
}

Upvotes: 2

Hussain Mansoor
Hussain Mansoor

Reputation: 3134

This lineif let articlesFromJSON = json["Articles"] as? [[String: Any]] { should be

if let articlesFromJSON = json["articles"] as? [[String: Any]] {

Upvotes: 2

Related Questions