Can't properly set image from JSON in tableView cell - json

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.

Related

How to return an array of objects using SearchBar and for loop

I am wanting the search bar to return the title for each object in the Array. I believe the problem is in my for loop but I am not completed sure. Hopefully, you guys are able to tell me what I am doing wrong.
I am searching through an API. This is the array I am attempting to search through
struct ProductResponse: Decodable {
let results: [SearchResults]
}
struct SearchResults: Decodable {
let title: String
let id:Int
}
I created a for loop to run through each object in the array and get the title and id.
func fetchProduct(productName: String) {
let urlString = "\(searchURL)&query=\(productName)&number=25"
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
// Create a URL
if let url = URL(string: urlString) {
//Create a URLSession
let session = URLSession(configuration: .default)
// Give the session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJSON(productTitle: safeData)
}
}
// Start the task
task.resume()
}
}
func parseJSON(productTitle: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(ProductResponse.self, from: productTitle)
print(decodedData.results)
} catch {
print(error)
}
}
}
I created this function to reload the data in my tableview. When I search for the product results in the Search Bar, my tableview doesn't return anything. The goal is to have my tableview return each result in a tableview cell
var listOfProducts = [SearchResults]() {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
self.navigationItem.title = "\(self.listOfProducts.count) Products found"
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
productSearch.delegate = self
}
func downloadJSON() {
guard let downloadURL = url else { fatalError("Failed to get URL")}
URLSession.shared.dataTask(with: downloadURL) { (data, Response, error) in
print("downloaded")
}.resume()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return listOfProducts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let product = listOfProducts[indexPath.row].title
cell.textLabel?.text = product
return cell
}
}
extension ProductsTableViewController: UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchBarText = searchBar.text {
searchRequest.fetchProduct(productName: searchBarText)
}
}
}
This is the result
enter image description here
If everything goes well and You got the data under "decodedData.results" of "parseJSON" method, And I saw "decodedData.results" and "listOfProducts" both variables are the same type of SearchResults. So you can just add the one line of under "parseJSON" method as follows:-
func parseJSON(productTitle: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(ProductResponse.self, from: productTitle)
print(decodedData.results)// it have some array data of type SearchResults
self.listOfProducts = decodedData.results
self.tableView.reloadData()
} catch {
print(error)
}
}

Swift: Array Struct Image not visible in TableView

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!
}

Why getting nil in collectionView image from json in swift

I have collectionview with image and label... I'm able to display text values from json to label and I'm getting all img urls from json to cellForItemAtindexPath but all those images i am unable to show in collectionview.. i have all 20 image urls in cellForItemAtindexPath i can see it in console but why i am unable to display them in collectionview.
here is my code:
import UIKit
import SDWebImage
struct JsonData {
var iconHome: String?
var typeName: String?
var id: String?
init(icon: String, tpe: String, id: String) {
self.iconHome = icon
self.typeName = tpe
self.id = id
}
}
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UITextFieldDelegate, URLSessionDelegate {
#IBOutlet weak var collectionView: UICollectionView!
var itemsArray = [JsonData]()
override func viewDidLoad() {
super.viewDidLoad()
homeServiceCall()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemsArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
let aData = itemsArray[indexPath.row]
cell.paymentLabel.text = aData.typeName
cell.paymentImage.sd_setImage(with: URL(string:aData.iconHome!)) { (_, error, _, _) in
if let error = error {
print(error)
}
}
print("tableview collection images \(String(describing: aData.iconHome))")
return cell
}
//MARK:- Service-call
func homeServiceCall(){
let urlStr = "https://********/webservices//getfinancer"
let url = URL(string: urlStr)
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let respData = data else {
return
}
guard error == nil else {
print("error")
return
}
do{
let jsonObj = try JSONSerialization.jsonObject(with: respData, options: .allowFragments) as! [String: Any]
let financerArray = jsonObj["financer"] as! [[String: Any]]
for financer in financerArray {
guard let id = financer["id"] as? String else { break }
guard let pic = financer["icon"] as? String else { break }
guard let typeName = financer["tpe"] as? String else { break }
print("the json icons \(String(describing: pic))")
let jsonDataObj = JsonData(icon: pic, tpe: typeName, id: id)
self.itemsArray.append(jsonDataObj)
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
catch {
print("catch error")
}
}).resume()
}
}
when i print image urls in cellForItemAtindexPath i got all 20 image urls in console.. but why i am unable to show them in collectionview.
i am getting output like below.. some images are showing and some are not if i give placeholder image in sd_setImage then it shoes placeholder image in collectionview why??
here is my output:
some images are coming and some are not but there are no nil images in server all images are coming in json.. i got stuck here from long time.. anyone please help me here.
Because you are using Swift so i recommend that you should use KingFisher instead of SDWebImage to handle images from urls.
I checked your code, everything is fine. However, when you load image from url, some of them throw this error:
A URL session error happened. The underlying error: Error Domain=NSURLErrorDomain Code=-1202 \"The certificate for this server is invalid. You might be connecting to a server that is pretending to be “anyemi.com” which could put your confidential information at risk."
This error happens for urls with domain anyemi.com.
For example:
https://anyemi.com/PaySTAR/images/LSPUBLICSCHOOL_icon.png
https://anyemi.com/PaySTAR/images/CDMA_icon.png
Urls with domain dev.anyemi.com work well. For example:
https://dev.anyemi.com/maheshbank/icons/electricity.png
https://dev.anyemi.com/maheshbank/icons/gas.png
Therefore the problem is in SSL configuration of your backend. For now, you can change the url with domain anyemi.com to dev.anyemi.com for testing and i believe that it will work well.

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()