I need help on how to solve this problem.
I tried to fetch data using json but when I tried to view in Table View its not showing.
I used the code below to test if table view is working and it works!
// self.clientList = ["Mango", "Banana", "Orange", "Guava", "Grapes"]
I used the code below to test if there's data returned from json. Still it works.
for item in jsonClientList {
let firstName = item["firstName"]
//Printing is working
print(firstName as! String)
}
Line not working! I dont know why. Its inside of loop but to data upon loading the table view.
Thanks in advance.
self.clientList.append(firstName as! String)
//---------------------------------
var clientList:[String] = []
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.clientList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "tblClientList")
cell.textLabel?.text = self.clientList[indexPath.row]
return cell
}
internal func jsonParser() -> Void{
//-------------------------
let postEndpoint: String = "http://domain.com/client"
let url = NSURL(string: postEndpoint)
let session = NSURLSession.sharedSession()
session.dataTaskWithURL(url!, completionHandler:
{
(data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
do{
let ipString = NSString(data:data!, encoding: NSUTF8StringEncoding)
if (ipString != nil) {
let jsonClientList = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSArray
for item in jsonClientList {
let firstName = item["firstName"]
//I tried to print the data from json and its working!
print(firstName as! String)
//Line not working
//I tried to insert the firstName to clientList array
self.clientList.append(firstName as! String)
}
}
//If I use this its working
// self.clientList = ["Mango", "Banana", "Orange", "Guava", "Grapes"]
}
} catch{
print("Something bad happed!")
}
}
).resume()
//--------------
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
jsonParser()
}
//---------------------------------
you forget to refresh the new data to show in table, do like
self.clientList.append(firstName as! String)
}
dispatch_async(dispatch_get_main_queue())
self.yourtableViewname.reloadData()
}
As mentioned in the other answer the issue is that the table view needs to be reloaded.
In Swift there is a more convenient way to populate the data source array without repeat loop using the map function.
It assumes – like in the question – that all dictionaries in jsonClientList contain a key firstName.
tableView is the name of the UITableView instance.
...
let jsonClientList = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! [[String:AnyObject]]
self.clientList = jsonClientList.map{ $0["firstName"] as! String }
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
} catch {
...
Reading the JSON with mutable containers is not needed in this case.
Related
Swift 5, iOS 12, Xcode 10
I finally implemented a search bar on my ListingsViewController and it's working really well -- but the initial data isn't populating into the table.
I realize that this is a clumsy implementation, but I'm learning as I go and I'm using only code that I can understand. I've been at this for two days -- I've tried creating a Struct and bringing in the data that way, but I can't even get an Array. I've tried bringing it in as an NSArray and an Array and an Object, but either I can't get the initial table to load, or I can't get data to parse out at all, or I can't get the search to work.
I suspect it has something to do with how, when or where I'm calling the loadData() function but I just can't figure it out.
class ListingsViewController: UITableViewController, UISearchResultsUpdating {
var tableData = [String]()
var filteredTableData = [String]()
var resultSearchController = UISearchController()
func updateSearchResults(for searchController: UISearchController) {
filteredTableData.removeAll(keepingCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (tableData as NSArray).filtered(using: searchPredicate)
filteredTableData = array as! [String]
self.tableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
tableData = [String]()
loadData()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
tableView.tableHeaderView = controller.searchBar
return controller
})()
tableView.reloadData()
}
func loadData() {
guard let url = URL(string: "--------------") else {return }
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: [])
guard let jsonArray = jsonResponse as? [[String:Any]] else { return }
for dic in jsonArray {
self.tableData.append(dic["name"] as! String)
// self.feedItems.append(Listing(id: (dic["id"] as! String), city_id: (dic["city_id"] as! String), category: (dic["category"] as! String), sub_category: (dic["sub_category"] as! String), name: (dic["name"] as! String), phone: (dic["phone"] as! String), email: (dic["email"] as! String), website: (dic["website"] as! String), address: (dic["address"] as! String), comment: (dic["comment"] as! String), recommendedby: (dic["recommendedby"] as! String)))
}
} catch let parsingError {
print("Error", parsingError)
}
}
self.tableView.reloadData()
task.resume()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (resultSearchController.isActive) {
return filteredTableData.count
} else {
return tableData.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.textColor = .white
cell.backgroundColor = .black
cell.tintColor = .lightText
if (resultSearchController.isActive) {
cell.textLabel?.text = filteredTableData[indexPath.row]
return cell
} else {
cell.textLabel?.text = tableData[indexPath.row]
return cell
}
}
}
I'm expecting the entire list of Listings to appear when I switch to the ListingsViewController from the tab bar. Instead, I get a blank table.
HOWEVER, If I tap into the search bar and start typing, though, I get matching results -- and when I cancel the search, I can see all of the results in the table.
(Also, when I tap into the search bar, my navigation bar goes away and doesn't come back, even when I cancel the search. Even if I switch to a different tab and come back. Haven't been able to figure that one out.)
You are missing the delegate and data source for the table view
add this:
class ListingsViewController: UITableViewController, UISearchResultsUpdating,UITableViewDelegate,UITableViewDataSource {
//your class code here
}
and on your viewDidLoad add this:
self.tableView.delegate = self
self.tableView.dataSource = self
that should work, Please notice that after you called the delegate and data source your cell for row and numbers of rows functions will be called so make sure the array you are using is not nil by then
I saw you haven't been missing delegate and data source for the table view.
You must register delegate and data source for viewcontroler. where you will display data.
Please insert this code on your viewDidLoad
self.tableView.delegate = self
self.tableView.dataSource = self
Change your load day to this
func loadData() {
guard let url = URL(string: "--------------") else’s {return }
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: [])
guard let jsonArray = jsonResponse as? [[String:Any]] else { return }
for dic in jsonArray {
self.tableData.append(dic[“name”] as! String
}
DispatchQueue.main.async {
print("call to reload data")
self.tableView.reloadData()
}
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
your are callig to reload data before the completion block, you can check it with the following print("calling to reloadData") then test my solution and see that works, make a print statamente for JsonArray to check where is called, you must reload the tableViewData after the async func is terminated.
I'm struggling with multithreading in news app. The thing is - my application freezes often when I scroll table view after data was parsed and loaded and its way too often. I think I'm some kind of wrong of reloading data every time.
First part:
final let urlString = "http://api.to.parse"
Here I create array of structs to fill in my data
struct jsonObjects {
var id : Int
var date : String
var title : String
var imageURL : URL
}
var jsonData = [jsonObjects]()
Here's my viewDidLoad of tableView
override func viewDidLoad() {
super.viewDidLoad()
// MARK : - Download JSON info on start
JsonManager.downloadJsonWithURL(urlString: urlString, сompletion: {(jsonArray) -> Void in
guard let data = jsonArray else { print("Empty dude"); return;}
for jsonObject in data {
if let objectsDict = jsonObject as? NSDictionary {
guard
let id = objectsDict.value(forKey: "id") as? Int,
let date = objectsDict.value(forKey: "date") as? String,
let titleUnparsed = objectsDict.value(forKey: "title") as? NSDictionary,
let title = (titleUnparsed as NSDictionary).value(forKey: "rendered") as? String,
let imageString = objectsDict.value(forKey: "featured_image_url") as? String,
let imageURL = NSURL(string: imageString) as URL?
else {
print("Error connecting to server")
return
}
There I go with appending filled structure to array:
self.jsonData.append(jsonObjects(id: id, date: date, title: title,
imageURL: imageURL))
}
}
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
})
and downloadJsonWithURL is simply:
class JsonManager {
class func downloadJsonWithURL(urlString: String, сompletion: #escaping (NSArray?) -> Void) {
guard let url = NSURL(string: urlString) else { print("There is no connection to the internet"); return;}
URLSession.shared.dataTask(with: url as URL, completionHandler: { (data, response, error) -> Void in
guard let parseData = data else { print("There is no data"); return;}
if let jsonObj = try? JSONSerialization.jsonObject(with: parseData, options: .allowFragments)
as? NSArray {
сompletion(jsonObj)
}
}).resume()
}
And finally - I input that in my TableViewCell:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jsonData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "newscell") as? NewsTableViewCell else {
fatalError("Could not find cell by identifier")
}
guard let imageData = NSData(contentsOf: jsonData[indexPath.row].imageURL) else {
fatalError("Could not find image")
}
cell.newsTitleLabel.text = self.jsonData[indexPath.row].title
cell.newsTitleLabel.font = UIFont.boldSystemFont(ofSize: 20.0)
cell.newsImageView.image = UIImage(data: imageData as Data)
return cell
}
So there are two questions: how should I distribute my threads and how should I call them so that I have smooth and nice tableview with all downloaded data? and how should I reload data in cell?
Your issue is caused by the imageData its blocking the main thread. The best way to solve this is to download all the images into an image cache. And I would most certainly remove the downloading of images from within the cellForRowAtIndexPath.
Downloading data, parsing in background thread, the updating the UI on main-thread.
Basically if you do correctly like this, everything will be okay.
So you may need to double check one more time if you are rendering UI on main-thread.
On the debugging panel, there's pause/play button.
So whenever your app frozen, try to pause the app immediately:
1) Then check if any of your UI method is running on background-thread.
2) Check if your downloading task or parsing json doing on main-thread.
If it falls under above cases, it needs to be correct.
I'm trying to find a way to parse through some Json data on reddit and display the information in a table view. (https://api.reddit.com).
So far this is what my code looks like:
var names: [String] = []
var comment: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://api.reddit.com")
do{
let reddit = try Data(contentsOf: url!)
let redditAll = try JSONSerialization.jsonObject(with: reddit, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String : AnyObject]
if let theJSON = redditAll["children"] as? [AnyObject]{
for child in 0...theJSON.count-1 {
let redditObject = theJSON[child] as! [String : AnyObject]
names.append(redditObject["name"] as! String)
}
}
print(names)
}
catch{
print(error)
}
}
//Table View
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
//Configure cells...
cell.textLabel?.text = names[indexPath.row]
cell.detailTextLabel?.text = comments[indexPath.row]
return cell
}
I know for a fact, the information is actually coming through the "redditALL" constant but i'm not sure what i'm doing incorrect after the JSONSerialization.
Also, i would really appreciate it if there was some kind of link to help me understand JSON Parsing in swift better, Thanks.
First of don't use Data(contentsOf:) to get JSON from URL because it will block your Main thread instead of that use URLSession.
Now to retrieve your children array you need to first access data dictionary because children is inside it. So try like this way.
let url = URL(string: "https://api.reddit.com")
let task = Session.dataTask(with: url!) { data, response, error in
if error != nil{
print(error.)
}
else
{
if let redditAll = (try? JSONSerialization.jsonObject(with: reddit, options: []) as? [String : Any],
let dataDic = redditAll["data"] as? [String:Any],
let children = dataDic["children"] as? [[String:Any]] {
for child in children {
if let name = child["name"] as? String {
names.append(name)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
task.resume()
JSON parsing in Swift (Foundation) is dirt-simple. You call JSONSerialization.jsonObject(with:) and you get back an "object graph". Usually it's a dictionary or array containing other objects. You have to know about the format of the data you're getting in order to cast the results to the proper types and walk the object graph. If you cast wrong your code will fail to run as expected. You should show us your JSON data. It's likely there is a mismatch between your JASON and your code.
I have just started working with Swift and am able to do some basic things. Right now I am trying to populate my UITableView with Json Data that I am successfully retrieving. Right now I have this simple Table that looks like this
That is a basic TableView that I was able to create with this code
#IBOutlet var StreamsTableView: UITableView!
let groceries = ["Fish","lobster","Rice","Beans"]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let mycell:UITableViewCell = StreamsTableView.dequeueReusableCell(withIdentifier: "prototype1", for: indexPath)
mycell.textLabel?.text = groceries[indexPath.row]
return mycell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return groceries.count
}
override func viewDidLoad() {
super.viewDidLoad()
StreamsTableView.dataSource = self
}
I now have a JsonRequest that I am completing successfully using this code below
override func viewDidLoad() {
super.viewDidLoad()
StreamsTableView.dataSource = self
// Do any additional setup after loading the view.
var names = [String]()
let urlString = "http://localhost:8000/streams"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
if let Streams = parsedData["Streams"] as! [AnyObject]? {
for Stream in Streams {
if let post = Stream["post"] as? String {
names.append(post)
}
}
}
} catch let error as NSError {
print(error)
}
print(names)
}
}).resume()
}
What I essentially like to do is put the value of
let post = Stream["post"] as? String
inside the TableView instead of the Groceries array . As I stated before the value is coming back from the Json, I just have not found any way that I could put that value inside the TableView any help would be great . I am using swift 3.0 .
Add reloading data code
DispatchQueue.main.async {
StreamsTableView.reloadData()
}
just after your for loop
for Stream in Streams { ...
if let Streams = parsedData["Streams"] as! [AnyObject]? {
for Stream in Streams {
if let post = Stream["post"] as? String {
names.append(post)
}
}
}
StreamsTableView.reloadData()
After loop done
StreamsTableView.reloadData()
update:
mycell.textLabel?.text = groceries[indexPath.row]
to
mycell.textLabel?.text = names[indexPath.row]
I'm populating my tableView with JSON data, most of the time the data shows but for some strange reason other times it doesn't. I tested the JSON data in Chrome and the info is there. I also made print statements to print the info after it has downloaded and it appears to download correctly. I can't figure out why 80% of the time the data populates the tableView correctly and 20% of the time it doesn't. Here is a sample of my code, there are many more cells but I shortened it to 2 for this example:
var task : NSURLSessionTask?
var newURL : String?
var bannerArray: [String] = []
var overViewArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
getJSON(newURL!)
}
func getJSON (urlString: String) {
let url = NSURL(string: urlString)!
let session = NSURLSession.sharedSession()
task = session.dataTaskWithURL(url) {(data, response, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.updateDetailShowInfo(data)
}
else {
"Not getting JSON"
}
}
}
task!.resume()
}
func updateDetailShowInfo (data: NSData!) {
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
guard let banner = jsonResult["banner"] as? String,
let overview = jsonResult["overview"] as? String
else { return }
_ = ""
print(overview)
bannerArray.append(banner)
overViewArray.append(overview)
}
catch {
print("It ain't working")
}
self.DetailTvTableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: return bannerArray.count
case 1: return overViewArray.count
default: fatalError("Unknown Selection")
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCellWithIdentifier("bannerCell", forIndexPath: indexPath) as! BannerCell
cell.bannerImage.sd_setImageWithURL(NSURL(string: bannerArray[indexPath.row]))
self.DetailTvTableView.rowHeight = 100
DetailTvTableView.allowsSelection = false
return cell
case 1:
let cell = tableView.dequeueReusableCellWithIdentifier("overviewCell", forIndexPath: indexPath) as! OverviewCell
let overViewText = overViewArray[indexPath.row]
if overViewText != "" {
cell.overView.text = overViewText
} else {
cell.overView.text = "N/A"
}
self.DetailTvTableView.rowHeight = 200
print(overViewArray[indexPath.row])
return cell
default: ""
}
return cell
}
I'm just doing this off the web. And I think there are some errors. You need to debug them yourself.
Your understanding of fetching the JSON and GCD is totally wrong. I believe these codes you got somewhere off the web. Go read up what is dispatch_async.
Basically, you need to create session to fetch JSON data, which you have done it correctly, however, within the NSJSONSerialization, you need to store them in a variable and append it to your array. This is fetched asynchronously. Your dispatch_async will reload data serially.
func getJSON (urlString: String) {
let url = NSURL(string: urlString)!
let session = NSURLSession.sharedSession()
task = session.dataTaskWithURL(url) {(data, response, error) in
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
guard let banner = jsonResult["banner"] as? String,
let overview = jsonResult["overview"] as? String
bannerArray.append(banner)
overViewArray.append(overview)
} dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.DetailTvTableView.reloadData()
}
else {
"Not getting JSON"
}
}
catch {
print("It ain't working")
}
}
}
task!.resume()
}