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.
Related
My app has a user account page that uses a tableview to return a timeline type detail. I'd like to have the actual logged-in user detail at the top in a table header.
This is handled on the useraccountViewController. I have added a function to the viewDidLoad called getUserAndTimeLine() which makes 2 api calls to my web-service to get the details for my logged in user (this view controller is opened by a segue from the login page).
I also have 2 structs one for the loggedUser, and one for the userPost that define what I am expecting to receive from the API calls. I also have arrays for the user and the posts based on the structs.
The getUserAndTimeLine() code is as follows:
//creates api url to get user
let url = URL(string: "myapi.url/getuser?uid=" + user)
//httpget sent to the api and listens for response
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error as Any)
}
else {
do{
//parses the json data
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! NSDictionary
//get the user data
let thisUser = parsedData["userName"] as? String
let thisUserImage = parsedData["userAvatar"] as? String
let thisLoggedUser = loggedUser( userAvatar: thisUserImage, userName: thisUser)
self.arrayOfLoggedUser.append(thisLoggedUser)
}
catch{
print("parse error")
}
}
}.resume()
//creates api url to get user TL
let tlurl = URL(string: "myapi.url?uid=" + user)
//httpget sent to the api and listens for response
URLSession.shared.dataTask(with:tlurl!) { (data, response, error) in
if error != nil {
print(error as Any)
}
else {
do{
//parses the json data
let parsedTLData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:AnyObject]
guard let posts = parsedTLData["timeline"] as? [[String:AnyObject]] else {return}
for post in posts {
let thisPost = postData(postText: post["postText"] as! String, postURL: post["postURL"] as! String, postImageURL: post["postImage"] as! String,
postDomainName: post["postSource"] as! String,
postDomainLink: post["postDomainLink"] as! String,
postPoster: post["postPoster"] as! String,
postPosterAvatar: post["postPosterAvatar"] as! String,
postDate: post["postCreateDate"] as! String)
self.arrayOfPostData.append(thisPost)
self.tableView.reloadData() }
}
catch{
print("parse error")
}
}
}.resume()
In the individual cells, I am successfully able to populate my tableview (timeline) with the data from arrayOfPostData via:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("postTimeLineCell", owner: self, options: nil)?.first as! postTimeLineCell
cell.postImage.sd_setImage(with: URL(string: arrayOfPostData[indexPath.row].postImageURL))
cell.postText.setTitle(arrayOfPost[indexPath.row].postText, for:.normal )
cell.postDate.text = arrayOfPostData[indexPath.row].postDate
// etc.
return cell
}
However I cannot return the header:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let loggedUserHeader = Bundle.main.loadNibNamed("userHeader", owner: self, options: nil)?.first as! userHeader
loggedUserHeader.userName.text = arrayOfLoggedUser[section].userName
return loggedUserHeader
}
as I get an error: EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0) which in the output is "fatal error: Index out of range"
Can anyone help out as I'm rather stuck!
Asynchronous data is almost always the problem.
The user array will be populated after the API call has completed - but you will be refreshing the tableView before that.
Simplest thing is to put a check in viewForHeaderInSection and if the array.count < section index then just return with nothing, or a default value
I´m building a widget for iOS with Swift. The main app´s purpose is to connect to a URL news feed and get the latest news, while the widget only get the title to display in a tableView in the Today view.
I´ve written this method for the widget in order to get the data to populate the table, but for some reason nothing is showing. I´ve tried to debug it, but being a widget it seems to be practically imposible.
This is the cellForRowAt, where I connect to the feed and try to extract data. The funny part is, the main app uses basically the same code and it works perfectly.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let urlRequest = URLRequest(url: URL(string: "https://newsapi.org/v1/articles?source=techcrunch&sortBy=top&apiKey=c64849bc30eb484fb820b80a136c9b0a")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let articlesFromJson = json["articles"] as? [[String: AnyObject]] {
if !(error != nil) {
var resultArray: NSMutableArray = NSMutableArray()
for articlesFromJson in articlesFromJson {
if let title = articlesFromJson["title"] as? String{
resultArray.add(title)
}
let array:NSArray = resultArray.reverseObjectEnumerator().allObjects as NSArray
resultArray = array as! NSMutableArray
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
}
}
}
//reload on main thread to speed it up
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
return cell
}
If someone can help me figure out where is the mistake it would be a huge help, i´ve been stuck on this issue for days now. Thanks
You want to make your network request outside of cellForRow and then reloadData once it's complete to have the tableView reload the cells which calls cellForRow.
store the array of data outside of request so you can reference it from outside the function.
var resultArray: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
func getData() {
let urlRequest = URLRequest(url: URL(string: "https://newsapi.org/v1/articles?source=techcrunch&sortBy=top&apiKey=c64849bc30eb484fb820b80a136c9b0a")!)
let task = URLSession.shared.dataTask(with: urlRequest) {[weak self] (data,response,error) in
guard let strongSelf = self else { return }
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let articlesFromJson = json["articles"] as? [[String: AnyObject]] {
if error == nil {
for articlesFromJson in articlesFromJson {
if let title = articlesFromJson["title"] as? String{
strongSelf.resultArray.add(title)
}
let array:NSArray = strongSelf.resultArray.reverseObjectEnumerator().allObjects as NSArray
strongSelf.resultArray = array as! NSMutableArray
DispatchQueue.main.async {
strongSelf.tableView.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
return cell
}
checks proper if TableView delegate or datasource proper connected. and check array count before load data in cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if (resultArray.count > 0){
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
}
else
{
print("Error: resultArray contain nil value ")
}
return cell
}
I'm getting error while trying to display an image in tableview.
It is displaying text successfully but it is not showing any images.
class ViewController: UITableViewController {
var ref: FIRDatabaseReference!
var refHandle: UInt!
var valueList = [Values]()
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
fetchFirebaseData()
}
func fetchFirebaseData() {
refHandle = ref.child("SwiftJson").observe(.childAdded, with:
{(snapshot) in
if let dictionary = snapshot.value as? [String : AnyObject] {
print(dictionary)
let value = Values() as AnyObject
value.setValuesForKeys(dictionary)
self.valueList.append(value as! Values)
self.tableView.reloadData()
}
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return valueList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! FTableViewCell
let value = valueList[indexPath.row]
cell.fTitle.text = value.title
let imageUrlString = valueList[indexPath.row]["image"] as! String
let imageURL = NSURL(string: imageUrlString)
let imageData = NSData(contentsOf: imageURL as! URL)
cell.fImage.image = UIImage(data: imageData as! Data)
return cell
}
}
Here I'm parsing data from Firebase database and trying to show it on tableview.
Probably the problem that you are having is basically that you are trying to access a subscript of a anyObject type. On swift, a anyObject doesn't has subscripts. I don't know why you are casting Values() as AnyObject, but thats is probably why you are getting this error.
Also, try to take a look better on the other questions. Your question was probably asked before.
You have:
var valueList = [Values]()
Then later:
let imageUrlString = valueList[indexPath.row]["image"] as! String
Your error message tells you exactly what is wrong:
“Type 'Values' has no subscript members”
valueList[indexPath.row] is of type Value which as far as I can guess, does not have a subscript defined in its class. Which is exactly what the error says. And you're trying to call a subscript on it:
valueList[indexPath.row]["image"]
Looking at your earlier code of when you assign Value's to the array, I'm guessing it would unwrap as [String : AnyObject]
So you need to:
if let value = valueList[indexPath.row] as? [String : AnyObject], let imageUrlString = value["image"] as? String {
let imageURL = NSURL(string: imageUrlString)
//etc, etc...
}
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 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.