Displaying JSON imageURL into UITableView using Alamofire and SwiftyJSON - json

I am currently trying to display the JSON data on to my UITableView. I've created a separate class which handles and declares the data being extracted from the API. I also created a custom cell to display the data. However I am not sure how to display the images of the API. I was able to display the title of the API using just a standard TableViewController without custom cells:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "recipeCell", for: indexPath)
cell.textLabel?.text = recipes = [indexPath.row].title
return cell
}
but when trying to create a custom cell the image uses an imageURL which works differently. Other tutorials and demo don't seem to be using Alamofire and SwiftyJSON.
I did read the documentation: https://github.com/SwiftyJSON/SwiftyJSON#integration but still not sure how to solve this issue. Please tell me how you'd get the API data to display on my table.
Class
import Foundation
import SwiftyJSON
class Recipe {
var title: String!
var image: URL?
init(json: JSON) {
self.title = json["title"].stringValue
self.image = json["image"].url
}
}
CustomCell
import UIKit
class RecipeCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var imgView: UIImageView?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
ViewController
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request(searchURL, method: .get, parameters: params, encoding: URLEncoding.default, headers: headers).response { [unowned self] response in
guard let data = response.data else { return }
let json = JSON(data: data)
for recipe in json.arrayValue {
let newRecipe = Recipe(json: recipe)
self.recipes.append(newRecipe)
}
for recipe in self.recipes {
print(recipe.title)
print(recipe.image)
}
}
}
// Display JSON Data into the tableView
func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recipes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "recipeCell", for: indexPath)
// What to write here?
return cell
}
Thank you in advance! :)

for this you have to make the extension of Imageview as
extension UIImageView {
func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit) {
contentMode = mode
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() { () -> Void in
self.image = image
}
}.resume()
}
func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloadedFrom(url: url, contentMode: mode)
}
}
then you have to write cellForRowAt method as
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "recipeCell", for: indexPath)
let recipe = recipes[indexPath.row]
let imagePath = recipe.image
cell.titleLabel.text = recipe.title
cell. imgView.downloadedFrom(link: imagePath)
return cell
}

Related

Type 'HeroStruct.Type' cannot conform to 'Decodable', how can i solve that?

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var hero = [HeroStruct]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
navigationController?.navigationBar.barTintColor = UIColor.systemYellow
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.systemYellow ]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: nil)
cell.textLabel?.text = ""
cell.backgroundColor = .systemYellow
return cell
}
func getJsonData(completion: #escaping () -> () ) {
let url = URL(string: "https://api.opendota.com/api/heroStats")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
if error != nil {
print(error?.localizedDescription)
}else {
do {
let result = try JSONDecoder().decode(HeroStruct.Type, from: data!)
}catch {
print(error)
}
}
}
}
}
import Foundation
struct HeroStruct : Decodable {
let localized_name : String
let primary_attr : String
let attack_type : String
let legs : Int
let img : String
}
First code block is my ViewController.swift page,
second code block is my HeroStruct.swift page,
I tried to get data from Json but i got error like this:
Type 'HeroStruct.Type' cannot conform to 'Decodable'
How can i solve this?
let result = try JSONDecoder().decode([HeroStruct].Type, from: data!)`
I tried write like this but doesn't work. Need help ,thanks.
Replace [HeroStruct].Type with [HeroStruct].self. Whenever you want to decode something, always use .self & not .Type.

How to pass a JSON decoded Array to another VC on swift

well Im trying to pass an array of articles which came from a news API to another VC so I will be able to display them on my TableView, now the problem is that the tableview is loaded before I updated my array, is there a way to update the array before the tableview is loaded?
MY CODE:
MY NewsViewController Code:
import UIKit
import Foundation
class NewsViewController: UITableViewController,NewsProtocol {
func didUpdateNewsArr(arr: [Articles]) {
newsArr = arr
print("I'm loaded first!!")
// print(newsArr[0].title)
}
var newsArr = [Articles]()
var newsManager = NewsManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "Hot News"
newsManager.newsProtocolDelegate = self
newsManager.performRequest()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}
}
// MARK: - UITableView Delegate&DataSource Methods:
extension NewsViewController {
// Return the number of rows for the table.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsArr.count
}
// Provide a cell object for each row.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Fetch a cell of the appropriate type.
let cell = tableView.dequeueReusableCell(withIdentifier: "newsCell", for: indexPath)
// Configure the cell’s contents.
cell.textLabel!.text = "Cell text"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
my networking file:
import Foundation
import UIKit
protocol NewsProtocol {
func didUpdateNewsArr(arr: [Articles])
}
struct NewsManager {
let newsURL = "https://newsapi.org/v2/everything?q=apple&from=2020-10-19&to=2020-10-19&sortBy=popularity&apiKey=009a08a56d664f1b92986e9cce27767b"
var newsProtocolDelegate: NewsProtocol?
func performRequest() {
if let url = URL(string: newsURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, respone, error) in
if error != nil {
print(error)
return
}
if let safeData = data {
self.parseJSON(newsData: safeData)
}
}
task.resume()
}
}
func parseJSON(newsData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(NewsData.self, from: newsData)
let articlesArr = decodedData.articles
newsProtocolDelegate?.didUpdateNewsArr(arr: articlesArr)
} catch {
print("")
}
}
}
I've solved the problem :) thanks fellas, all was needed was tableView.reload() to reload the cells, due to the fact that the JSON request took a while and the tableView was made up before the request, I've used DispatchQue :)

Loading tableView after JSON only

I realize the tableView.reloadData() get's called in the JSON completion block to reload the tableView with the data received; I was wondering if there was a way to load the tableView only after this completion block has finished. What is happening is that the tableView first loads empty with default cells and a few seconds later the reloadData() method gets called inside the completion block and the tableView reloads and the data appears with the custom cells. I want to load the tableView ONLY when and after the data is received. What approach can I take? Is there a way to load the view only after this is completed? I basically don't want to have the user look at a blank table for a few seconds and wait for the data to appear. Here is my viewController code and simple structs to hold the model for the data.
viewController:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var users = [User]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else { return }
let recieved = try JSONDecoder().decode([User].self, from: data)
self.users = recieved
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error as NSError {
print("Error: \(error.description)")
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.name.text = users[indexPath.row].name
cell.eMail.text = users[indexPath.row].email
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
Structs:
struct User: Decodable {
let id: Int
let name: String
let username: String
let email: String
let company: Company
}
struct Company: Decodable {
let name: String
let catchPhrase: String
let bs: String
}
Since you're essentially waiting for a network call before the data can be displayed, why not display a spinner or activity indicator on a view on top of the tableview then dismiss this when the data has been parsed successfully (or handle any errors). The alternative could be to request the data before the view is loaded in another class.
I think you can add activity indicator in your UITableView. So User will not see only blank UITableView. Or you can add background image in your UITableView, You can show it if the data is still empty and hide it after JSON decoded.
for reference background image in UITableView, you can see here
As per your suggestions; here is the route I took using an activityIndicator. I set a UIView onto of the tableView, then added an activityIndicator on top of that UIView and I also added a simple UILabel next to the activityIndicator with the string "Loading". I used propertyAnimator inside the JSON task after data had been received and after reloading the tableView, then stopping activityIndicator, fading out the UIView to show the tableView and then removing the UIView from the superView. Here is the code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var users = [User]()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var loadingView: UIView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else { return }
let recievedUsers = try JSONDecoder().decode([User].self, from: data)
self.users = recievedUsers
DispatchQueue.main.async {
self.tableView.reloadData()
if self.loadingView.alpha == 1.0 {
self.activityIndicator.stopAnimating()
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0,
delay: 0.0,
options: [],
animations: {
self.loadingView.alpha = 0.0
},
completion: { (position) in
if position == .end {
self.loadingView.removeFromSuperview()
}
})
}
}
} catch let error as NSError {
print("Error: \(error.description)")
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.name.text = users[indexPath.row].name
cell.eMail.text = users[indexPath.row].email
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}

Table empty when populating cells from json

I am trying to populate a table with json content. Everything seems to work fine except that the table is not showing any data. Actually, the code shown below should display the "title" information of each json data array into one cell. See line
cell.textLabel?.text = myNewsItems[indexPath.row].title
However, from what I can see in the console output, I can verify that the news array is parsed like expected (see Checkpoint: print(myNewsS)).
Any idea what I am missing?
Swift4
import UIKit
// structure from json file
struct News: Codable{
let type: String
let timestamp: String
let title: String
let message: String
}
class HomeVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
var myTableView = UITableView()
var myNewsItems: [News] = []
override func viewDidLoad() {
super.viewDidLoad()
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: 150, width: displayWidth, height: displayHeight - barHeight))
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
myTableView.dataSource = self
myTableView.delegate = self
self.view.addSubview(myTableView)
// JSON
let url=URL(string:"https://api.myjson.com/bins/ywv0k")
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data, response, error) in
// check status 200 OK etc.
guard let data = data else { return }
do {
let myNewsS = try
JSONDecoder().decode([News].self, from: data)
print(myNewsS)
DispatchQueue.main.async {
self.myTableView.reloadData()
}
} catch let jsonErr {
print("Error json:", jsonErr)
}
}
task.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myNewsItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = myNewsItems[indexPath.row].title
return cell
}
}
Assign the array
myNewsItems = myNewsS
DispatchQueue.main.async {
self.myTableView.reloadData()
}

Swift 4 saving data from json to an array to show it in TableView

I'm trying to save the data from func getCoinData to an array sympolsCoin and array sympolsCoin to use it in my TableView
I create this class in the same ViewController.swift file :
struct Coin: Decodable {
let symbol : String
let price_usd : String }
And this in my View controller class :
var coins = [Coin]()
var sympolsCoin = [String]()
var priceUSDcoin = [String]()
func getCoinData(completion: #escaping () -> ()) {
let jsonURL = "https://api.coinmarketcap.com/v1/ticker/"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data!)
for info in self.coins {
self.sympolsCoin.append(info.symbol)
self.priceUSDcoin.append(info.price_usd)
print("\(self.sympolsCoin) : \(self.priceUSDcoin)")
completion()
}
}
catch {
print("Error is : \n\(error)")
}
}.resume()
}
And when i use the array in my TableView i got blank table !
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BitcoinTableViewCell", for: indexPath) as! BitcoinTableViewCell
cell.coinNameLable.text = sympolsCoin[indexPath.row]
cell.priceLable.text = priceUSDcoin[indexPath.row]
return cell
}
Since you are using JSONDecoder the entire logic to create and populate sympolsCoin and priceUSDcoin is pointless and redundant.
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case symbol, priceUSD = "price_usd"
}
let symbol : String
let priceUSD : String
}
var coins = [Coin]()
The completion handler is redundant, too. Just reload the table view on the main thread after receiving the data:
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v1/ticker/"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
In viewDidLoad load the data
override func viewDidLoad() {
super.viewDidLoad()
getCoinData()
}
In cellForRow update the UI
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BitcoinTableViewCell", for: indexPath) as! BitcoinTableViewCell
let coin = coins[indexPath.row]
cell.coinNameLable.text = coin.symbol
cell.priceLable.text = coin.priceUSD
return cell
}
Create an Outlet of tableView in ViewController Class and give it name "tableView" then
Try this code: Swift 4
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v1/ticker/"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data!)
for info in self.coins {
self.sympolsCoin.append(info.symbol)
self.priceUSDcoin.append(info.price_usd)
print("\(self.sympolsCoin) : \(self.priceUSDcoin)")
self.tableView.reloadData()
}
}
catch {
print("Error is : \n\(error)")
}
}.resume()
}
Call this function in ViewDidLoad like this
override func viewDidLoad() {
super.viewDidLoad()
getCoinData()
}
You need to update the tableView from the main thread. As a good lesson to learn: Always update the UI from the Main Thread. Always.
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data!)
for info in self.coins {
self.sympolsCoin.append(info.symbol)
self.priceUSDcoin.append(info.price_usd)
DispatchQueue.main.async {
self.tableView.reloadData()
}
print("\(self.sympolsCoin) : \(self.priceUSDcoin)")
completion()
}
}
There is, however another problem with your code the way you have your labels setup won't work. TableViewCells get reused so I'm guessing you have #IBOutlets for them somewhere else. What you should do is declare a label constant in cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
let coinNameLabel = cell.viewWithTag(100) as! UILabel
coinNameLabel.text = sympolsCoin[indexPath.row]
let priceNameLabel = cell.viewWithTag(101) as! UILabel
priceNameLabel.text = priceUSDcoin[indexPath.row]
}
The above code assumes you've setup two labels with the tags 100 and 101 in your storyboard(assuming your using one)
**
// First View Controller
//
//
//
import UIKit
struct Countory : Decodable {
let name: String
let capital: String
let region: String
let alpha2Code: String
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var listArr = [Countory]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
let url = "https://restcountries.eu/rest/v2/all"
let urlObj = URL(string: url)!
URLSession.shared.dataTask(with: urlObj) {(data, responds, Error) in
do {
self.listArr = try JSONDecoder().decode([Countory].self, from: data!)
for country in self.listArr {
print("Country",country.name)
print("###################")
print("Capital",country.capital)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
} catch {
print(" not ")
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
cell.label1.text = "Name: \(listArr[indexPath.row].name)"
cell.lable2.text = listArr[indexPath.row].capital
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let homeView = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
homeView.res = listArr[indexPath.row].region
homeView.alpha = listArr[indexPath.row].alpha2Code
self.navigationController?.pushViewController(homeView, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
// SecondViewController
class SecondViewController: UIViewController {
#IBOutlet weak var label4: UILabel!
#IBOutlet weak var label3: UILabel!
var res = ""
var alpha = ""
override func viewDidLoad() {
super.viewDidLoad()
self.label3.text = res
self.label4.text = alpha
}
}
**