Issue retrieving firebase child node swift - json

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

Related

How do I populate a tableview with JSON data from Alamofire?

Before I state my problem, I want to let everyone know that I am new to the coding environment that is Swift, so forgive me for my lack of knowledge. Currently, I am having trouble populating the cells of a tableview using Alamofire based on the data that is returned from a JSON URL. When I run the app in a simulator, data is displayed in the console, but the app crashes with a SIGABRT error. For reference, instead of using a viewcontroller with a tableview element inside, I am using a tableviewcontroller. Here is my code thus far:
import UIKit
import Alamofire
class TableViewController: UITableViewController {
var responseArray: NSArray = []
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request("https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/10/explicit.json").responseJSON { response in
if let json = response.result.value {
print(json)
self.responseArray = json as! NSArray
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return responseArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "top10", for: indexPath)
// Configure the cell...
let whichSong = responseArray[(indexPath as NSIndexPath).row]
let artistName = (whichSong as AnyObject)["artistName"] as? String
cell.textLabel?.text = artistName
return cell
}
The crash occurs because the root object of the JSON is a dictionary (represented by {}) not an array.
First of all declare a type alias for a JSON dictionary and the data source array as native type, an array of JSON dictionaries:
typealias JSONDictionary = [String:Any]
var responseArray = [JSONDictionary]()
Then parse the JSON and reload the table view, you want probably the array for key results:
Alamofire.request("https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/10/explicit.json").responseJSON { response in
if let json = response.result.value as? JSONDictionary,
let feed = json["feed"] as? JSONDictionary,
let results = feed["results"] as? [JSONDictionary] {
print(results)
self.responseArray = results
self.tableView.reloadData()
}
}
Then show the data in cellForRow
let song = responseArray[indexPath.row]
cell.textLabel?.text = song["artistName"] as? String
Okay so firstly change
let cell = tableView.dequeueReusableCell(withIdentifier: "top10", for: indexPath)
to
let cell = tableView.dequeueReusableCell(withIdentifier: "top10")
However, with this, cell will be cell?, you will have to return cell!.
Next in your Alamofire response,
if let json = response.result.value {
print(json)
self.responseArray = json as! NSArray
self.reloadData()
//If above line doesn't work, try tableView.reloadData()
}
Why?
The Alamofire request is "asynchronous", meaning it executes codes while your app is doing other things. Therefor, it is likely that you are setting that array after your table is loaded, hence the reloadData()
Replace the below line
let cell = tableView.dequeueReusableCell(withIdentifier: "top10", for: indexPath)
with
let cell = tableView.dequeueReusableCell(withIdentifier: "top10")

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.

Problems with adding value to an array with array.append() in Swift

in the moment we're programming a Swift App for iOS in which we want to get data of our JSON Website (MySql database) into the TableViewCell. The problem is by appending the text values of the strings for the label in the cell. Swift can import the JSON values into the name variable but I cant assign it to the text array for the cells. I havent no syntax errors, but the data[0] Variable print sth. as "123". Why it is 123? The test Value is "Test". I don't now where the problem by appending the value to the array is, that the result is 123 after that. Please help.
Here is the sourcecode:
class listViewViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var data:[String?] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let myUrl = URL(string: "");//Empty link for this question
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"
let postString = "lid=1";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
print("error=\(error)")
return
}
print("response = \(response!)")
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let Name = parseJSON["Name"] as? String
print("\(Name)")//Test
self.data.append(Name!)
print("\(data![0])" as String)//123
}
} catch {
print(error)
}
}
task.resume()
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! listViewTableViewCell
print("\(data[indexPath.row])")
let dataCell = data[indexPath.row]
cell.listViewCell.text = dataCell
return cell
}
}
this because your array properties and data callback block parameter have the same name "data". in your code you user print("(data![0])" as String) instead of print("(self.data![0])" as String) => you have to add self.
then you can optimise your code like this (it's optional : it's just like a code review ;) )
try to do this
- change your array type to String like this
var data = [String]()
- webService callback change your code like this :
if let parseJSON = json {
if let Name = parseJSON["Name"] as? String{
print("\(Name)")
self.data.append(Name)
print("\(self.data.last)")//123
}
}
When you append to your data array you use self.data but you then print from data which is the parameter to the inner function. You add and print from different arrays.

How to load JSON into TableView?

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

GET works only once

I'm new to iOS programming, so my question might not be complicated but I'm still struggling to find the best solution. Any help will be highly appreciated!
I'm trying to send GET request every time the user opens the app. I wrote the function loadMenu() that collects the data from a json file on the server and populates the table in the app.
The problem is that if I update the json file, it's not reflected in the app. If feels like the loadMenu() part of the code is just ignored.
Here's my code:
import UIKit
class TableViewControllerNew: UITableViewController {
var names = [String]()
var mealDescription = [String]()
var price = [Double]()
override func viewDidLoad() {
super.viewDidLoad()
loadMenu()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
var new = NSUserDefaults.standardUserDefaults().objectForKey("names") as! [String]
print("test: \(new.count)")
return new.count
}
func loadMenu() {
print("viewDidLoad works")
// Send HTTP GET
let myUrl = NSURL(string: "http://localhost/myPi/selection/wheyStationSelection.json");
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
dispatch_async(dispatch_get_main_queue()) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
if let meals = json["meals"] as? [[String: AnyObject]] {
for meal in meals {
if let name = meal["name"] as? String {
self.names.append(name)
//NSUserDefaults.standardUserDefaults().setObject(self.names, forKey: "test1") as! [String]
//var new = NSUserDefaults.standardUserDefaults().objectForKey("test1") as? [String]
//print(new)
}
if let mealDescription = meal["mealDescription"] as? String {
self.mealDescription.append(mealDescription)
}
if let price = meal["price"] as? Double {
self.price.append(price)
}
}
}
} catch {
print("error serializing JSON: \(error)")
}
NSUserDefaults.standardUserDefaults().setObject(self.names, forKey: "test1") as! [String]
//print(self.names)
//print(self.mealDescription)
//print(self.price)
}
}).resume()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdenifier = "MealTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdenifier, forIndexPath: indexPath) as! MealTableViewCell
var new = NSUserDefaults.standardUserDefaults().objectForKey("names") as! [String]
let name = new[indexPath.row]
//print(name)
cell.mealNameLabel.text = name
return cell
}
#david is right, if you place your loadMenu() method in viewDidAppear() it will be called each and every time your view appears. You can read more about the various lifecycle phases of a UIViewController here
One other thing. It is not clear to me whether your loadMenu() isn't called every time or whether you are just not seeing the updated content.
I can see that you are not reloading your table view when the JSON has been updated. Therefore your TableView don't know that any updates has occurred and will not render again and you won't see any updates.
Therefore, right after this line:
NSUserDefaults.standardUserDefaults().setObject(self.names, forKey: "test1")
You should tell your tableView to reload itself like so:
tableView.reloadData()
So you have:
NSUserDefaults.standardUserDefaults().setObject(self.names, forKey: "test1")
tableView.reloadData()
That should cause all your "render the TableView" methods to be called again, but this time with the new data and you should see the updated content.
Hope this helps you.
If you call loadMenu() in viewDidAppear(animated:) instead of viewDidLoad(), then it will be called every time you leave your app and reopen it.