How to display JSON image on custom cell of tableview in Swift? - ios

I'm new into coding, learning how to parse JSON image into table view
able to display the labels but not able to display the image file. How to display it? I used the code given below please check it.
import UIKit
class ViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
var dataArray = [[String:AnyObject]]()
#IBOutlet weak var myTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/photos")! //change the url
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "GET" //set http method as POST
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [[String:Any]] {
self.dataArray = json as [[String : AnyObject]]
DispatchQueue.main.async {
self.myTable.reloadData()
}
print(json)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 250
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 250
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id") as! ModTableViewCell
cell.labout.text = String(describing: dataArray[indexPath.row]["id"]!)
cell.imagethum.image = UIImage(named :dataArray[indexPath.row]["thumbnailUrl"]! as! String)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "story") as? FinalViewController
var selectedindex = indexPath.row
vc?.jarray = dataArray
vc?.selectedindex1 = selectedindex
self.navigationController?.pushViewController(vc!, animated: true)
}
}

You need to download your image at first.
The basic solution is:
if let url = URL(string: "YOUR_URL") {
if let data = try? Data(contentsOf: url) {
cell.imagethum.image = UIImage(data: data)
}
}
For more advanced solution take a look on SDWebImage framework ( for example ) - it's beginner-friendly.

You need to download the image using thumbnailUrl String that you're getting from JSON response.
Replace the implementation of cellForRowAt method with the following code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id") as! ModTableViewCell
cell.labout.text = String(describing: dataArray[indexPath.row]["id"] ?? "")
if let urlString = dataArray[indexPath.row]["thumbnailUrl"] as? String, let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
if let data = data {
cell.imagethum.image = UIImage(data: data)
}
}.resume()
}
return cell
}
Also, don't use forced unwrapping (!) so extensively. It might result in crashing the app unnecessarily.

You can try this
We use this SDWebImage pod to load images from URL. This pod provides an async image downloader with cache support.
Example Of SDWebImage as below
let img1 = savedInspofflineData[indexPath.row].image1
if img1 == ""{
//Error
}else{
let imageUrl = URL(string: img1!)!
cell.img1.sd_setImage(with: imageUrl, placeholderImage: UIImage(named: "logo_grey"), options: .refreshCached, completed: nil)
}

Related

How to parse nested JSON array in TableView Swift?

I am using an endpoint that returns a JSON as response. The problem is response json is a huge data to process for me. From that I want to show all the Surah(englishName) name to display in the tableview. I tried my best as a new bee to iOS development. Please take a look at my snippet and let me know where i am doing wrong.
my Json data here:
ViewController code:
var surahName = [Surah]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: JSON parse
func parseJSON() {
let url = URL(string: "https://api.alquran.cloud/v1/quran/ar.alafasy")
guard url != nil else{
print("URL Founr Nill")
return
}
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil && data != nil{
do{
self.surahName = try JSONDecoder().decode([Surah].self, from: data!)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}catch{
print(error)
}
}
}.resume()
}
//MARK: Tableview delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return surahName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:QuranAudioCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! QuranAudioCell
let arrdata = surahName[indexPath.section].data.surahs
cell.nameLbl.text = arrdata[indexPath.row].englishName
return cell
}
Problem is its not printing anything in the tablview.
Change first line as
var surahNames = [EnglishName]()
Inside do-catch block change
self.surahName = try JSONDecoder().decode([Surah].self, from: data!)
into
let response = try JSONDecoder().decode(Surah.self, from: data!)
self.surahName = response.data.surahs
Now inside cellForRowAtIndexPath do this
let surah = surahName[indexPath.row]
cell.nameLbl.text = surah.englishName

error : type "UserAccView" does not conform to protocol 'UITableViewDataSource'

So I am trying to add some data returned from a function and I can only access that data from inside that function so I ended up putting the table inside the function but after I did so I received the error above.
Any ideas?
This is my code:
import Foundation
import UIKit
class UserAccView: UIViewController , UITableViewDataSource {
#IBAction func GetUserInfo(_ sender: UIButton) {
guard let url = URL(string: "https://goollyapp.azurewebsites.net/api/v0.1/Goolly/User/218910182109") else{return}
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let response = response {
print (response)
}
if let data = data {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
guard let data_array = json as? NSArray else
{
return
}
for i in 0 ..< data_array.count
{
if let data_object = data_array[i] as? NSDictionary
{
if let Body = data_object["id"] as? String,
let InfoId = data_object["TransDate"] as? String,
let Title = data_object["Debt"] as? String,
let UserId = data_object["Crdit"] as? String,
let InfoType = data_object["Desc"] as? String
{}
}
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (data?.count)!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell.textLabel?.text = "cells"
return cell
}
}.resume()
}
}
Why you have added the dataSource methods inside your Api Call ? Write those methods outside of your GetUserInfo IBAction.
Secondly, now you want to reload the tableview. For that create IBOutlet for tableview first and when response comes from the api you can reload the tableview after filling the response in your data array.
Lastly don't use var cell = UITableViewCell() like this in cellForRowAt. It will freeze your tableview . Use it like this
let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as UITableViewCell.
Hope it helps you

How do I fix laggy UITableView scrolling performance when downloading JSON?

In my application, I download a JSON file off of the internet and fill up a UITableView with items from the file. It does work well, and there are no problems or errors, but the scrolling performance is very laggy, and the UI glitches out a tiny bit.
I assume this is because of the images that I'm downloading from the JSON file, so I've looked into multi-threading, but I don't think I am doing it right because it does load much faster, but scrolling performance is still the same as before.
Can somebody please tell me how to fix this? This UITableView is the most important thing in the app, and I have been spending much time on trying to fix it. Thank you!
Here is my code-
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var nameArray = [String]()
var idArray = [String]()
var ageArray = [String]()
var genderArray = [String]()
var descriptionArray = [String]()
var imgURLArray = [String]()
let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
final let urlString = "https://pbsocfilestorage.000webhostapp.com/jsonDogs.json"
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithURL()
// Activity Indicator
myActivityIndicator.center = view.center
myActivityIndicator.hidesWhenStopped = true
myActivityIndicator.startAnimating()
view.addSubview(myActivityIndicator)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func downloadJsonWithURL() {
let url = NSURL(string:urlString)
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) ->
Void in
print("Good so far...")
if let jsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
print(jsonObj!.value(forKey: "dogs"))
if let dogArray = jsonObj!.value(forKey: "dogs") as? NSArray {
print("Why u no work!")
for dog in dogArray {
if let dogDict = dog as? NSDictionary {
if let name = dogDict.value(forKey: "name") {
self.nameArray.append(name as! String)
}
if let name = dogDict.value(forKey: "id") {
self.idArray.append(name as! String)
}
if let name = dogDict.value(forKey: "age") {
self.ageArray.append(name as! String)
}
if let name = dogDict.value(forKey: "gender") {
self.genderArray.append(name as! String)
}
if let name = dogDict.value(forKey: "image") {
self.imgURLArray.append(name as! String)
}
if let name = dogDict.value(forKey: "description") {
self.descriptionArray.append(name as! String)
}
OperationQueue.main.addOperation ({
self.myActivityIndicator.stopAnimating()
self.tableView.reloadData()
})
}
}
}
}
}).resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let imgURL = NSURL(string: imgURLArray[indexPath.row])
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell") as! TableViewCell
URLSession.shared.dataTask(with: (imgURL as! URL), completionHandler: {(data, resp, error) -> Void in
if (error == nil && data != nil) {
OperationQueue.main.addOperation({
cell.dogNameLabel.text = self.nameArray[indexPath.row]
cell.idLabel.text = self.idArray[indexPath.row]
cell.ageLabel.text = self.ageArray[indexPath.row]
cell.genderLabel.text = self.genderArray[indexPath.row]
print("Cell info was filled in!")
if imgURL != nil {
let data = NSData(contentsOf: (imgURL as? URL)!)
cell.dogImage.image = UIImage(data: data as! Data)
}
})
}
}).resume()
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDog" {
if let indexPath = self.tableView.indexPathForSelectedRow{
let detailViewController = segue.destination as! DetailViewController
detailViewController.imageString = imgURLArray[indexPath.row]
detailViewController.nameString = nameArray[indexPath.row]
detailViewController.idString = idArray[indexPath.row]
detailViewController.ageString = ageArray[indexPath.row]
detailViewController.descriptionString = descriptionArray[indexPath.row]
detailViewController.genderString = genderArray[indexPath.row]
}
}
}
}
There is a big mistake. You are loading data with dataTask but you aren't using that returned data at all. Rather than you are loading the data a second time with synchronous contentsOf. Don't do that.
And don't update the labels in the asynchronous completion block. The strings are not related to the image data.
This is more efficient:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let imgURL = URL(string: imgURLArray[indexPath.row])
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! TableViewCell
cell.dogNameLabel.text = self.nameArray[indexPath.row]
cell.idLabel.text = self.idArray[indexPath.row]
cell.ageLabel.text = self.ageArray[indexPath.row]
cell.genderLabel.text = self.genderArray[indexPath.row]
print("Cell info was filled in!")
URLSession.shared.dataTask(with: imgURL!) { (data, resp, error) in
if let data = data {
OperationQueue.main.addOperation({
cell.dogImage.image = UIImage(data: data)
})
}
}.resume()
return cell
}
Note: You are strongly discouraged from using multiple arrays as data source. It's very error-prone. Use a custom struct or class. And create imgURLArray with URL instances rather than strings. This is also much more efficient.
Nevertheless, you should use a download manager which caches the images and cancels downloads if a cell goes off-screen. At the moment each image is downloaded again when the user scrolls and cellForRow is called again for this particular cell.

TableView assigning image to cell in TVC

Having a problem with this code. Basically i'm trying to populate a table cell using an image im pulling from twitter. The url field here has the value http://pbs.twimg.com/profile_images/796924570150301696/35nSG5nN_normal.jpg but for some reason the print("REACHED") is never printed. Any help/suggestions appreciated!
code snippet:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: tIdentifier, for: indexPath) as! TweetCell
let tweet = tweets[indexPath.section][indexPath.row]
let url = tweet.user.profileImageURL!
print(url.absoluteString)
let data = try? Data(contentsOf: url)
if (data == nil){
} else {
print("REACHED")
cell.avatarImage = UIImage(data: data!)
}
cell.tweet = tweets[indexPath.section][indexPath.row]
return cell
}
This worked for me:
func example() {
let cell = UITableViewCell()
let url = URL(string: "http://pbs.twimg.com/profile_images/796924570150301696/35nSG5nN_normal.jpg")
do {
let data = try Data(contentsOf: url!)
print("REACHED")
cell.imageView?.image = UIImage(data: data)
} catch {
print("received this error:\n\(error.localizedDescription)")
}
}
If it doesn't work right away, at least you'll have an error message to help you figure it out. Good luck!
Edit:
You should make sure you have updated your Info.plist to include an entry for:
App Transport Security Settings
Without this you will not have access to other sites.
Transport security has blocked a cleartext HTTP
Some tips for an easy lifeā€¦
Don't force unwrap
Don't download on the main queue
Don't expose your cell's IBOutlets
let imageQueue = DispatchQueue(label: "imageQueue", qos: DispatchQoS.background)
class TweetCell: UITableViewCell {
#IBOutlet fileprivate var avatarImage: UIImageView!
var tweet: Tweet {
didSet {
guard let url = tweet.user.profileImageURL else { return }
loadImage(url: url)
}
}
fileprivate func loadImage(url: URL) {
imageQueue.async {
do {
let data = try Data(contentsOf: url)
DispatchQueue.main.async {
self.avatarImage.image = UIImage(data: data)
}
} catch {
// Handle error
}
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: tIdentifier, for: indexPath) as! TweetCell
cell.tweet = tweets[indexPath.section][indexPath.row]
return cell
}

passing NSArray into tableview swift 3.0

I'm trying to connect my swift ios app to mysql with php... and the upon receiving the JSON from the php.. i converted it into nsarray and tried to populate my tableview with it.. however it doesnt seem to show anything in the tableview when i run it.... the data is successful in passing into the NSArray as i see my result when i print(values).. it just cant seem to show up on my tableview and i dont know why
#IBOutlet weak var tableView: UITableView!
var values:NSArray = []
#IBAction func php(_ sender: Any) {
let url = NSURL(string: "http://localhost/try.php")
let data = NSData(contentsOf: url! as URL)
values = try! JSONSerialization.jsonObject(with: data! as Data, options:JSONSerialization.ReadingOptions.mutableContainers) as! NSArray
print (values)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
cell.descriptionView.text = (values[indexPath.row] as AnyObject) as? String
return cell
}
That's the recommended way to load data over the network.
As mentioned in the comment do not use NSArray, NSData and NSURL in Swift 3. URLSession loads the data asynchronously and in the background. The table view is reloaded on the main thread.
var values = [[String:String]]()
#IBAction func php(_ sender: AnyObject) {
let url = URL(string: "http://localhost/try.php")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
do {
self.values = try JSONSerialization.jsonObject(with: data!, options:[]) as! [[String:String]]
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
task.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
let item = values[indexPath.row]
cell.descriptionView.text = item["title"]
return cell
}

Resources