How to load JSON into TableView? - json

I am trying to load an exercises JSON into a table view, i have a service function that gets the data from a source as JSON, and a view controller with a table that I want to load the info into. There are no errors in the code however the table loads blank rows, the debug section shows the JSON data just fine via a print command. Im a beginner so im sure im missing a core element, but cant work it out!
api service
class ApiService {
static var swiftyJsonVar:JSON?
class func getExerciseData() {
Alamofire.request("https://wger.de/api/v2/exercise/?format=json").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
swiftyJsonVar = JSON(responseData.result.value!)
print(swiftyJsonVar ?? nil)
}
}
}
View Controller
class ExerciseDatabaseController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var ExerciseSearchField: UISearchBar!
#IBOutlet weak var ExercisesTableView: UITableView!
var arrRes = [[String:AnyObject]]() // Array of dictionary
override func viewDidLoad() {
super.viewDidLoad()
let arrRes = ApiService.getExerciseData()
if let resData = ApiService.swiftyJsonVar?["exercise"].arrayObject {
self.arrRes = resData as! [[String:AnyObject]]
}
if self.arrRes.count > 0 {
self.ExercisesTableView.reloadData()
}
print(arrRes)
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrRes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
var dict = arrRes[indexPath.row]
cell.textLabel?.text = dict["name"] as? String
cell.detailTextLabel?.text = dict["description"] as? String
return cell
}

You should be loading your JSON asynchronously, which means you should have a closure in the method that makes your alamofire call.
class ApiService {
class func getExerciseData(completion: #escaping ([[String: AnyObject]]) -> ()) {
Alamofire.request("https://wger.de/api/v2/exercise/?format=json").responseJSON { (responseData) -> Void in
guard let jsonResponse = responseData.result.value else {
//possibly put some sort of protection, or what you want to do if there is not a response here
return
}
//instead of creating a variable for swiftyJsonVar in this class,
//you want to use a completion to send the array of dictionaries to the tableview asynchronously,
//that way it doesn't load blank
//I'm not super familiar with swifty json(sorry). What I normally do is below.
let swiftyJsonVar = JSON(jsonResponse)
guard let dictArray = swiftyJsonVar["exercise"].arrayObject as? [[String: AnyObject]] else {
//some sort of protection here if this fails
return
}
completion(dictArray)
}
}
So now we have made our asynchronous call(generally you want to do this whenever you are displaying information visually from an internet call that was not already preloaded/saved somewhere in app).
Next, we want to display this information in our tableview upon tableview load.
class ExerciseDatabaseController: UIViewController, UITableViewDataSource, UITableViewDelegate {
//these should start with lower cases(exerciseSearchField), never uppercased
#IBOutlet weak var ExerciseSearchField: UISearchBar!
#IBOutlet weak var ExercisesTableView: UITableView!
var arrRes = [[String:AnyObject]]() // Array of dictionary
override func viewDidLoad() {
super.viewDidLoad()
//you said you would use these delegates up top when you created the class, so you have to set them
ExercisesTableView.delegate = self
ExercisesTableView.dataSource = self
fetchData()
// Do any additional setup after loading the view.
}
//this method will make the api call
//you'll notice that if you set breakpoints, it will reach the end of the method before hitting self?.arrRes = dictArray
//this is normal and how asynchronous calls work, look into tableview threading for a deeper explanation of why that is. It is super important to understand threading in iOS
//once it gets data back from the API call, it will go to the main thread and tell the tableview to reload with that data
func fetchData() {
ApiService.getExerciseData { [weak self] (dictArray) in
self?.arrRes = dictArray
print(self?.arrRes)
if self?.arrRes.count > 0 {
DispatchQueue.main.async {
self?.ExercisesTableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrRes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
var dict = arrRes[indexPath.row]
cell.textLabel?.text = dict["name"] as? String
cell.detailTextLabel?.text = dict["description"] as? String
return cell
}
You'll see I used [weak self] above. For more of an explanation of why that is necessary with asynchronous internet calls/whenever using closures, you can read here:
http://krakendev.io/blog/weak-and-unowned-references-in-swift
There are a lot of other resources for reading about weak and strong references/parent child stuff in iOS with a quick google search. Also, pursue researching asynchronous/synchronous in iOS. Both of these topics are incredibly important to learn when beginning.

Reload your tableView once the JSON data from your asynchronous request is received. So your
self.ExercisesTableView.reloadData()
will go inside
Alamofire.request("https://wger.de/api/v2/exercise/?format=json").responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
swiftyJsonVar = JSON(responseData.result.value!)
print(swiftyJsonVar ?? nil)
}
}

Related

How to pass a JSON decoded Array to another VC on swift

well Im trying to pass an array of articles which came from a news API to another VC so I will be able to display them on my TableView, now the problem is that the tableview is loaded before I updated my array, is there a way to update the array before the tableview is loaded?
MY CODE:
MY NewsViewController Code:
import UIKit
import Foundation
class NewsViewController: UITableViewController,NewsProtocol {
func didUpdateNewsArr(arr: [Articles]) {
newsArr = arr
print("I'm loaded first!!")
// print(newsArr[0].title)
}
var newsArr = [Articles]()
var newsManager = NewsManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "Hot News"
newsManager.newsProtocolDelegate = self
newsManager.performRequest()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}
}
// MARK: - UITableView Delegate&DataSource Methods:
extension NewsViewController {
// Return the number of rows for the table.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsArr.count
}
// Provide a cell object for each row.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Fetch a cell of the appropriate type.
let cell = tableView.dequeueReusableCell(withIdentifier: "newsCell", for: indexPath)
// Configure the cell’s contents.
cell.textLabel!.text = "Cell text"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
my networking file:
import Foundation
import UIKit
protocol NewsProtocol {
func didUpdateNewsArr(arr: [Articles])
}
struct NewsManager {
let newsURL = "https://newsapi.org/v2/everything?q=apple&from=2020-10-19&to=2020-10-19&sortBy=popularity&apiKey=009a08a56d664f1b92986e9cce27767b"
var newsProtocolDelegate: NewsProtocol?
func performRequest() {
if let url = URL(string: newsURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, respone, error) in
if error != nil {
print(error)
return
}
if let safeData = data {
self.parseJSON(newsData: safeData)
}
}
task.resume()
}
}
func parseJSON(newsData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(NewsData.self, from: newsData)
let articlesArr = decodedData.articles
newsProtocolDelegate?.didUpdateNewsArr(arr: articlesArr)
} catch {
print("")
}
}
}
I've solved the problem :) thanks fellas, all was needed was tableView.reload() to reload the cells, due to the fact that the JSON request took a while and the tableView was made up before the request, I've used DispatchQue :)

Loading tableView after JSON only

I realize the tableView.reloadData() get's called in the JSON completion block to reload the tableView with the data received; I was wondering if there was a way to load the tableView only after this completion block has finished. What is happening is that the tableView first loads empty with default cells and a few seconds later the reloadData() method gets called inside the completion block and the tableView reloads and the data appears with the custom cells. I want to load the tableView ONLY when and after the data is received. What approach can I take? Is there a way to load the view only after this is completed? I basically don't want to have the user look at a blank table for a few seconds and wait for the data to appear. Here is my viewController code and simple structs to hold the model for the data.
viewController:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var users = [User]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else { return }
let recieved = try JSONDecoder().decode([User].self, from: data)
self.users = recieved
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error as NSError {
print("Error: \(error.description)")
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.name.text = users[indexPath.row].name
cell.eMail.text = users[indexPath.row].email
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
Structs:
struct User: Decodable {
let id: Int
let name: String
let username: String
let email: String
let company: Company
}
struct Company: Decodable {
let name: String
let catchPhrase: String
let bs: String
}
Since you're essentially waiting for a network call before the data can be displayed, why not display a spinner or activity indicator on a view on top of the tableview then dismiss this when the data has been parsed successfully (or handle any errors). The alternative could be to request the data before the view is loaded in another class.
I think you can add activity indicator in your UITableView. So User will not see only blank UITableView. Or you can add background image in your UITableView, You can show it if the data is still empty and hide it after JSON decoded.
for reference background image in UITableView, you can see here
As per your suggestions; here is the route I took using an activityIndicator. I set a UIView onto of the tableView, then added an activityIndicator on top of that UIView and I also added a simple UILabel next to the activityIndicator with the string "Loading". I used propertyAnimator inside the JSON task after data had been received and after reloading the tableView, then stopping activityIndicator, fading out the UIView to show the tableView and then removing the UIView from the superView. Here is the code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var users = [User]()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var loadingView: UIView!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else { return }
let recievedUsers = try JSONDecoder().decode([User].self, from: data)
self.users = recievedUsers
DispatchQueue.main.async {
self.tableView.reloadData()
if self.loadingView.alpha == 1.0 {
self.activityIndicator.stopAnimating()
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0,
delay: 0.0,
options: [],
animations: {
self.loadingView.alpha = 0.0
},
completion: { (position) in
if position == .end {
self.loadingView.removeFromSuperview()
}
})
}
}
} catch let error as NSError {
print("Error: \(error.description)")
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.name.text = users[indexPath.row].name
cell.eMail.text = users[indexPath.row].email
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}

Issue retrieving firebase child node swift

Xcode 9 - Swift 4
No Permissions set on Firebase Data - read/write everyone
I imported json data into firebase and my data looks like this..
I am trying to get to to the title of the jobs listed in FireBase Database, place the list of titles in an array and into a tableView and it will not return anything
My swift code looks like this..
import UIKit
import FirebaseDatabase
class PostViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var jobPostsTableView: UITableView!
var ref: DatabaseReference?
var databaseHandle: DatabaseHandle = 0
var searchJSONQuery : String = ""
var jobsData = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
jobPostsTableView.delegate = self
jobPostsTableView.dataSource = self
//Set the Firebase Reference
ref = Database.database().reference()
// Retreive the posts and listen for changes
databaseHandle = (ref?.child("positions/title").observe(.childAdded, with: { (snapshot) in
//Code to execute when a child is added under "positions"
//Take the value from the snapshot and add it to the jobsData array
let list = snapshot.value as? String
if let actualList = list {
self.jobsData.append(actualList)
self.jobPostsTableView.reloadData()
}
}))!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jobsData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell")
cell?.textLabel?.text = jobsData[indexPath.row]
return cell!
}
}
When using child() you only go one level down the tree at the time. Because you have a lot of positions you can not simply access the titles by using child("title").
When calling the observeSingleEvent you're looking for the values of the key you have stated in you database-reference.
The way written below you get a snapshot of all the values beneath your "positions" key. Therefore you use the for-loop to access the "title" value of every single object.
You should write it as a separate function and call it from viewDidLoad() rather than write the firebase code inside viewDidLoad itself.
func retrieveJobTitles(){
let positionRef = ref.child("positions")
positionsRef.observeSingleEvent(of: .value, with: { (snapshot) in
// Iterate through all of your positions
for child in snapshot.children.allObjects as! [DataSnapshot] {
let position = child as! DataSnapshot
let positionsInfo = position.value as! [String: Any]
let jobTitle = positionsInfo["title"] as! String
if jobTitle != nil {
self.jobsData.append(jobTitle)
}
}
self.jobPostsTableView.reloadData()
})
}
}

swift 3.0: tableview cannot show data which received as JSON through web api

i started to learn IOS development using swift 3.0.i built a simple app to call web api to query data from server database. i can get the json data and parsed it into string array. the App can print the array, but it cannot show in the tableview. it confused me several days and i searched some examples and answers on internet but still couldn't work out it.
My codes as below:
class LocationTableViewController: UITableViewController {
var names: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
//——————————————————————————get the data from web api and using json parsing————————————————————————
let config = URLSessionConfiguration.default // Session Configuration
let session = URLSession(configuration: config) // Load configuration into Session
let url = URL(string: "http://XXXXXXX/api/mylocations")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
if error != nil {
print(error!.localizedDescription)
} else {
do {
var jsonResult: NSMutableArray = NSMutableArray()
let jsonArray = try JSONSerialization.jsonObject(with: data!, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
jsonResult = jsonArray.mutableCopy() as! NSMutableArray
var jsonElement: NSDictionary = NSDictionary()
for i in 0..<jsonResult.count {
jsonElement = jsonResult[i] as! NSDictionary
if let name = jsonElement["Name"] as? String
{
// print(id)
// print(name)
// print(address)
// print(latitude)
// print(longitude)
// print("-------")
self.names.append(name)
}
// self.tableView.reloadData()
// print(self.names)
}
print(self.names)
// can print the string array data like [“name1”,”name2”,”name3”]
} catch {
print("error in JSONSerialization")
}
}
})
task.resume()
//-------------- ——— result is [] it seems the above code didn't put the string array to names.——————————————
print(self.names)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count;
}
internal override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for:
indexPath as IndexPath) as UITableViewCell
// Configure the cell...
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
Can anyone help me have a look?
Put self.tableView.reloadData() after print print(self.names).
At the point where you have commented...
result is [] it seems the above code didn't put the string array to
names
This line of code is being executed before the data has been downloaded within the completion handler, so we wouldn't expect to see anything here. You will note that it is working on the other print that you have within the completion handler.
The tableView.reloadData() at the end of the completion handler should be working.
Are you sure that you have the delegates set up correctly for the tableView? What do you see if you comment out the download task, and simply set
names = ["Tom", "Dick", "Harry"]
within viewDidLoad ? If that doesn't work, it's a problem with the delegates.

What is the convention when populating a tableview from JSON in swift?

I have seen someone create an object to receive the JSON data, then have an array of that object. and upon receiving new data from the JSON the Object array updates and the table view reloads.
How would i do this? I didn't really understand it, but i now need it as i need to receive data from PHP then parse it in Xcode onto a table view.
If you could, i would really be grateful if you could also show any optimisation tips.
I think they used the didSet variable in their code.
Thank you for reading!
This is what you meant with didSet right?
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var myTableView: UITableView!
private var dataArray: [String] = [String]() {
didSet {
myTableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let jsonRequest: NSURLRequest = NSURLRequest(URL: NSURL(string: "yourEndPoint")!)
NSURLConnection.sendAsynchronousRequest(jsonRequest, queue: NSOperationQueue.mainQueue()) { (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if let jsonArray: [[String: String]] = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableLeaves, error: nil) as? [[String: String]] {
for jsonObject in jsonArray {
if let stringFromKey: String = jsonObject["yourKey"] as String? {
self.dataArray.append(stringFromKey)
}
}
}
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = dataArray[indexPath.row]
return cell
}
}
You just need to exchange the JSON Parsing and Array type to fit your purpose