Swift: Array Struct Image not visible in TableView - json

me again with a new Question.
I´m gathering Picture Data from some JSON and fill it in an Array with a Struct as Datatype.
Everything works fine but when I try to display it in a TableView the Image is Missing - rest of Data is Displaying as wanted.
But the Images seems to store in the Array too (See print after guard ---new pic added---).
Also Dispatch Group is completing with "All Done".
Would be great if you could take a look and tell me what I´m doing wrong.
Array:
var memeRawData: [MemeRawData] = []
Struct:
struct MemeRawData {
let name: String?
let url: String?
let img: UIImage?
}
Dispatch Group to gather Data from JSON and Download Image and append it to array:
var meme = MemeRawData(name: "", url: "", img: UIImage())
var memeName = ""
var memeURL = ""
var memeIMG = UIImage()
let g = DispatchGroup()
MemeAPI.requestAPIImageData { (imgData, error) in
imgData?.data.memes.forEach{
memeData in
g.enter()
memeName = memeData.name!
memeURL = memeData.url!
MemeAPI.requestAPIImageFile(url: URL(string: memeData.url!)!) { (image, error) in
guard let image = image else {
print("PIC IS NIL")
return
}
memeIMG = image
print("-----NEW PIC ADDED-------")
}
meme = MemeRawData(name: memeName, url: memeURL, img: memeIMG)
self.memeRawData.append(meme)
g.leave()
}
g.notify(queue:.main) {
print("All done")
}
}
Function to get the Image from URL:
class func requestAPIImageFile(url: URL, completionHandler: #escaping (UIImage?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completionHandler(nil, error)
return}
let downloadedImage = UIImage(data: data)
completionHandler(downloadedImage, nil)
}
task.resume()
}
And to complete it tableView dequeue:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "memeAPICell")
cell?.textLabel?.text = memeRawData[indexPath.row].name
cell?.imageView?.image = memeRawData[indexPath.row].img
return cell!
}
Like I said JSON Parsing is working fine since I can display all URL´s and Names - but there seems to be a Problem with the Images. Also Console is not Printing out any Error.
Thanks in advance!

You need to set the image in main thread,
write this function,
func setImage(imageView:UIImageView,url:URL){
let task = URLSession.shared.dataTask(with: url){ (data, response, error) in
if error != nil{
return
}
DispatchQueue.main.async {
let img = UIImage(data: data!)
imageView.image = img
}
}
task.resume()
}
then call it in cellForItemAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "memeAPICell")
cell?.textLabel?.text = memeRawData[indexPath.row].name
self.setImage(imageView:cell!.imageView!, url: URL(string: memeRawData[indexPath.row].url)!)
return cell!
}

Set up your data array as you are already, and then for each element, download the image. it won't be available the first time you show the tableview, but that's ok. You have options for how you show that something is coming, here's a simple way to do it.
override func viewDidLoad() {
super.viewDidLoad()
// set up your meme data as you do already
// go get images in the background
for index in 0...memeRawData.count-1 {
print("Go get data for \(index)")
getImage(index: index, url: URL(string: memeRawData[index].url ?? "")!)
}
}
func getImage(index: Int, url:URL){
let task = URLSession.shared.dataTask(with: url){ (data, response, error) in
if error != nil{
return
}
DispatchQueue.main.async {
print("ready to reload /(index)")
// now that you have the image, add it to your data array
self.memeRawData[index].img = UIImage(data: data!)
// now reload the specific row in your tableview
self.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}
}
task.resume()
}
then all you have to do is modify the way you display the tableview cell. In this example, if there's no image available, I have changed the text display, but you could replace the image with a picture to show waiting, and if you have that included in your assets, you can have it available to display.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "memeAPICell")
if memeRawData[indexPath.row].img == nil
{
cell?.textLabel?.text = memeRawData[indexPath.row].name! + " awaiting image ..."
}
else
{
cell?.textLabel?.text = memeRawData[indexPath.row].name ?? "title"
}
cell?.imageView?.image = memeRawData[indexPath.row].img
return cell!
}

Related

Can't properly set image from JSON in tableView cell

I have to get crytocurrencies logos and set them in tableView cell. JSON has the following structure
"data": {
"1": {
"id": 1,
"name": "Bitcoin",
"symbol": "BTC",
"logo": "https://s2.coinmarketcap.com/static/img/coins/64x64/1.png",
},
"2": {
"id": 2,
"name": "Litecoin",
"symbol": "LTC",
"logo": "https://s2.coinmarketcap.com/static/img/coins/64x64/2.png"
...}
This's my model:
struct Data: Decodable {
let data: [String: Id]
}
struct Id: Decodable {
let logo: String
}
According to documentation I can fetch logos for cryptocurrencies by adding ids to this URL: https://pro-api.coinmarketcap.com/v1/cryptocurrency/info?id=1,2,3,4,5... Since I need top 100 currencies, I'm getting them this way and then sending through Notification to TableViewController.
class NetworkManager {
func loadData() {
let ids = (1...100).map { String($0) }.joined(separator: ",")
guard let baseURL = URL(string: "https://pro-api.coinmarketcap.com/v1/cryptocurrency/info?id=\(ids)") else {
print("Wrong URL")
return
}
let finalURL = baseURL
var request = URLRequest(url: finalURL)
request.addValue("MyApiKey", forHTTPHeaderField: "X-CMC_PRO_API_KEY")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let jsonData = data {
do {
let cryptoLogo = try JSONDecoder().decode(Data.self, from: jsonData)
NotificationCenter.default.post(name: .getLogos, object: cryptoLogo)
}
catch {
print(error)
}
}
}
dataTask.resume()
}
}
To display logos I'm using ImageView extension:
extension UIImageView {
func imageFromUrl(urlString: String) {
if let url = URL(string: urlString) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async() {
self.image = UIImage(data: data)
}
}
task.resume()
}
}
}
The problem is that I can print logo URLs, but can't set them properly in tableView cell. Could you please say what did I do wrong? What also worries me, it's that I get logos in not ascending order as I get in Postman. How can I sort string logos array?
#objc func getLogo(notification: Notification) {
if let responce = notification.object as? Data {
for value in responce.data.values {
data.append(value)
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let crypto = data[indexPath.row]
if let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? TableViewCell {
cell.imageCell.imageFromUrl(urlString: crypto.logo)
return cell
}
return UITableViewCell()
}
Use KingFisher or SDWebImage to load image from url. Just one line of code
cell.imgView.sd_setImage(with: URL(string: ""))
SDWebImage provides us to cache images automatically
By loading your images like so, you'll run into issues where images are being loaded in the wrong cell due to:
the time required to load them,
your images not being cached,
your cells being dequeued as you scroll but the associated loading tasks not being cancelled.
If you dynamically load images in a TableView, I strongly recommend that you use a library like KingFisher to easily load and cache your images, and cancel requests as cells are being dequeued.
With KingFisher imported, you can create a extension like so:
public extension UIImageView {
func loadKingfisherImage(url: String) {
self.kf.cancelDownloadTask()
self.kf.indicatorType = .activity
self.kf.setImage(with: ImageResource(downloadURL: URL(string: url)!, cacheKey: url)) { result in
switch result {
case .success(let value):
print("Task done for: \(value.source.url?.absoluteString ?? "")")
case .failure(let error):
print("Failed to load image: \(error.localizedDescription)")
}
}
}
}
Usage :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let crypto = data[indexPath.row]
if let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as? TableViewCell {
cell.imageCell.loadKingfisherImage(url: crypto.logo)
return cell
}
return UITableViewCell()
}
Refer to https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet if you need more features.

I'm having troubles displaying an image from JSON in a Table View

I'm trying to display images that comes from an API. The images are inside an URL and I want to fill a Table View with all the array, but it shows only one image at the Table View.
Here's my code:
struct Autos {
let Marca:String
let Modelo:String
let Precio:String
let RutaImagen:String
init?(_ dict:[String:Any]?){
guard let _dict = dict,
let marca=_dict["Marca"]as?String,
let modelo=_dict["Modelo"]as?String,
let precio=_dict["Precio"]as?String,
let rutaImagen=_dict["RutaImagen"]as?String
else { return nil }
self.Marca = marca
self.Modelo = modelo
self.Precio = precio
self.RutaImagen = rutaImagen
}
}
var arrAutos = [Autos]()
func getImage(from string: String) -> UIImage? {
// Get valid URL
guard let url = URL(string: string)
else {
print("Unable to create URL")
return nil
}
var image: UIImage? = nil
do {
// Get valid data
let data = try Data(contentsOf: url, options: [])
// Make image
image = UIImage(data: data)
}
catch {
print(error.localizedDescription)
}
return image
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 9
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "carsCell", for: indexPath) as! CarsDetailTableViewCell
let url = URL(string: "http://ws-smartit.divisionautomotriz.com/wsApiCasaTrust/api/autos")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
guard let dataResponse = data, error == nil else {
print(error?.localizedDescription ?? "Response Error")
return
}
do {
let jsonResponse = try JSONSerialization.jsonObject(with: dataResponse, options: []) as? NSArray
self.arrAutos = jsonResponse!.compactMap({ Autos($0 as? [String:String])})
DispatchQueue.main.async {
// Get valid string
let string = self.arrAutos[indexPath.row].RutaImagen
if let image = self.getImage(from: string) {
// Apply image
cell.imgCar.image = image
}
cell.lblBrand.text = self.arrAutos[indexPath.row].Marca
cell.lblPrice.text = self.arrAutos[indexPath.row].Precio
cell.lblModel.text = self.arrAutos[indexPath.row].Modelo
}
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
return cell
}
The JSON serialization is working fine, because the other data is showed correctly at the table view, the issue is with the image, because in the table view only appears one image, the other rows are empty. Does anyone have an advise?
I think you should download your full data before loading tableview and reload tableview in the completion handler. Call loadData() method in your viewDidLoad().
fileprivate func loadData() {
let url = URL(string: "http://ws-smartit.divisionautomotriz.com/wsApiCasaTrust/api/autos")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
guard let dataResponse = data, error == nil else {
print(error?.localizedDescription ?? "Response Error")
return
}
do {
let jsonResponse = try JSONSerialization.jsonObject(with: dataResponse, options: []) as? NSArray
self.arrAutos = jsonResponse!.compactMap({ Autos($0 as? [String:String])})
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
For loading images in tableView cell, download the image in background thread and then update the imageView in the main thread.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "carsCell", for: indexPath) as! CarsDetailTableViewCell
// Get valid string
let string = self.arrAutos[indexPath.row].RutaImagen
//print(string)
cell.lblBrand.text = self.arrAutos[indexPath.row].Marca
cell.lblPrice.text = self.arrAutos[indexPath.row].Precio
cell.lblModel.text = self.arrAutos[indexPath.row].Modelo
let url = URL(string: string)
if url != nil {
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
if data != nil {
cell.imgCar.image = UIImage(data:data!)
}
}
}
}
return cell
}
Hope this will work.

Fill a tableview from JSON with Swift

I'm a total swift novice and even the simplest things I can do. For example, I would like to show in a UItableview a list of names that come from a JSON file, but I am not able to do so.
Specifically, my JSON returns the following:
[{"id_centro":"3","0":"3","nombre":"Colegio Vistarreal","1":"Colegio Vistarreal"},{"id_centro":"1","0":"1","nombre":"IES ITC Sistemas","1":"IES ITC Sistemas"}]
And I have implemented my UITableViewController in the following way:
import UIKit
class TCentrosController: UITableViewController {
var centrosList = [String] ()
override func viewDidLoad() {
super.viewDidLoad()
Dologin()
}
func Dologin(){
let url = URL (string: "https://neton.es/WS_neton/lista_centros.php")
let session = URLSession.shared
let request = NSMutableURLRequest (url: url!)
request.httpMethod = "POST"
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
guard let _:Data = data else{
return
}
do{
let json = try JSONSerialization.jsonObject(with: data!, options: []) as! [[String:Any]]
if json.isEmpty{
DispatchQueue.main.async {
let alertController = UIAlertController(title: "Atención", message: "No hay centros disponibles", preferredStyle: .alert)
let yesAction = UIAlertAction(title: "OK", style: .default)
{ (action ) -> Void in
print("Centros no operativos")
}
alertController.addAction(yesAction)
self.present(alertController, animated: true, completion: nil)
}
return
//Si el json no está vacío, sigue por aquí
}else{
let nombreCentro:String = json[1]["nombre"] as! String
self.centrosList.append(nombreCentro)
}
}
catch{
print("Error")
}
})
task.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return centrosList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "centros_lista", for: indexPath) as! CeldaCentrosTableViewCell
cell.labelCell.text = centrosList[indexPath.row]
return cell
}
}
But it does not return anything to me by screen. In fact, if I do a print (centrosList.count) it tells me that zero for five times.
As you can see, I do not have much idea and I sure have more than one fault that I am not able to see. By the way, I'm programming with swift 3
Thanks, any help is appreciated
It seems that you are not reloading the data to tableView on received it from server.
The general steps to be followed is
set delegate, dataSource for the tableView.
Implement all the required delegate and dataSource methods.
fetch the data from the server and store it to array
reload the tableView.
So, you should add this line below
self.centrosList.append(nombreCentro)
Add this
DispatchQueue.main.async {
self.tableView.reloadData()
}
Try and share the results.

Swift 4, Json data doesn't appear in cells

I can't put data to the cells, I searched in different tutorials and it should work , I checked in debug area and data are downloaded but doesn't exist in cells, I tried also with custom cells but it doesn't work too. I have not got any error message, simply empty cells. Do you know maybe what can cause this issue? I spended much time for searching solution but I can't find anything, on every tutorial people do this similar to me.
struct Country: Decodable {
let name: String
}
class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var countries = [Country]()
var liczba = Int()
#IBOutlet var tv: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
json() {
self.tv.reloadData()
}
tv.dataSource = self
tv.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return liczba
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = countries[indexPath.row].name
return cell
}
func json (completed: #escaping()->()) {
let jsonUrl = "https://restcountries.eu/rest/v2/all"
let url = URL(string: jsonUrl)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.countries = try JSONDecoder().decode([Country].self, from: data!)
let numer = self.countries.count
self.liczba = numer
}
catch {
print("error")
}
}.resume()
}
}
It seems that you are having a problem with the sequentiality of your code. You are calling reloadData on your tableview but at that moment you don't have set the datasource yet.
try this:
tv.dataSource = self
tv.delegate = self
json() {
self.tv.reloadData()
}
You are never calling the completed closure of your json method, so self.tv.reloadData() will never be executed.
Add completed() after self.liczba = numer.
Since you are not returning anything from the closure I recommend to delete the completion handler and reload the table view on the main thread within the closure.
And don't forget to handle a potential error
func json() {
let jsonUrl = "https://restcountries.eu/rest/v2/all"
let url = URL(string: jsonUrl)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
if let error = error { print(error); return }
do {
self.countries = try JSONDecoder().decode([Country].self, from: data!)
self.liczba = self.countries.count
DispatchQueue.main.async {
self.tv.reloadData()
}
}
catch {
print("error")
}
}.resume()
}
and call the method in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
tv.dataSource = self // I'd connect datasource and delegate in Interface Builder
tv.delegate = self
json()
}

No Data in array github Api Swift

How do I get the "names" from the if let statement into my tableview? The code triggers the else block right now. I am trying to parse the name data from the github api. Here's the code:
import UIKit
import Foundation
class ViewController: UITableViewController {
var tableArray = [String] ()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.tableView.delegate = self
parseJSON()
}
func parseJSON () {
let url = URL(string: "https://api.github.com")
let task = URLSession.shared.dataTask(with: url!) {(data, response, error ) in
guard error == nil else {
print("returned error")
return
}
guard let content = data else {
print("No data")
return
}
guard let json = (try? JSONSerialization.jsonObject(with: content,
options: JSONSerialization.ReadingOptions.mutableContainers)) as?
[String: Any] else {
print("Not containing JSON")
return
}
if let array = json["name"] as? [String] {
self.tableArray = array
} else {
print("Name is blank")
}
print(self.tableArray)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
task.resume()
}
}
extension ViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
cell.textLabel?.text = self.tableArray[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableArray.count
}
}
Right now it's displaying "name is blank" in the console. I am trying to get the names of the users and display them in a tableview. Other url's seem to work, but I can't seem to figure out Github. Thanks for the help.
Your mistakes is.
1. Used wrong url.
2. Wrong mapping response
https://api.github.com Return Api list.
https://api.github.com/users/ Return user list.
Function fetchNameOfUsers
1. Implement request by use "user list" url.
2. Mapping the response by use "user list" structure.
func fetchNameOfUsers() {
guard let url = URL(string: "https://api.github.com/users") else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
return // Error
}
guard let data = data, let _ = response else {
return // No data
}
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] else {
return // Not [String: Any] list
}
let nameOfUsers = json.compactMap {
$0["login"] as? String
}
let displayedNameOfUsers = nameOfUsers.joined(separator: ", ")
print(displayedNameOfUsers)
}.resume()
}
To see result call function fetchNameOfUsers.
fetchNameOfUsers()