Reputation: 11
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
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
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
Reputation: 3134
This lineif let articlesFromJSON = json["Articles"] as? [[String: Any]] {
should be
if let articlesFromJSON = json["articles"] as? [[String: Any]] {
Upvotes: 2