So I'm saving a dictionary to my firebase. The dictionary is located in a custom class I made called FoodItem. Here's the lines where I save the dictionary:
let favRef = self.ref.childByAppendingPath("urlhidden")
favRef.setValue(foodItem.toAnyObject())
foodItem is a FoodItem object, here's the part of my FoodItem class that implements .toAnyObject()
var key: String
var ref: Firebase?
var name: String
var description: String
var minCal: Int
var maxCal: Int
var containsNuts: Bool
var vegetarian: Bool
var price: Double //**NOTE THAT PRICE IS A DOUBLE**
func toAnyObject()->[String:AnyObject]{
return ["name":self.name,
"price":self.price,
"description":self.description,
"minCal":self.minCal,
"maxCal":self.maxCal,
"containsNuts":self.containsNuts,
"vegetarian":self.vegetarian]
}
So those bits of code combined saves my data to my firebase. Works as it should. But when I retrieve the data, my price variable is a string? I'll explain...
Here's where I retrieve the data:
func getCurrentOrder(){
let uid = ref.authData.uid
ref = Firebase(url: "urlhidden")
ref.observeEventType(.Value, withBlock: { snapshot in
var newItems = [FoodItem]()
for item in snapshot.children {
let foodItem = FoodItem(snapshot: item as! FDataSnapshot)
newItems.append(foodItem)
}
self.order = newItems
self.tableView.reloadData()
})
}
That FoodItem constructor you see with the snapshot takes the data from my firebase and assigns my FoodItem variables to those values in my firebase. Here's that code:
init(snapshot: FDataSnapshot) {
key = snapshot.key
name = snapshot.value["name"] as! String
price = snapshot.value["price"] as! Double //**NOTE STILL A DOUBLE**
description = snapshot.value["description"] as! String
minCal = snapshot.value["minCal"] as! Int
maxCal = snapshot.value["maxCal"] as! Int
containsNuts = snapshot.value["containsNuts"] as! Bool
vegetarian = snapshot.value["vegetarian"] as! Bool
ref = snapshot.ref
}
However when I run my code, I see that it prints the double as a String
As you can see, there are clearly quotes around the Double variable "price".
And I get this error:
Could not cast value of type '__NSCFString' (0xff0ee0) to 'NSNumber' (0x13ff81c).
With this line of code highlighted in red:
price = snapshot.value["price"] as! Double
Which is located inside the FoodItem constructor with the snapshot as I posted above.
Any Ideas???
*EDIT: It does the same thing for my int variables. Same error message
It never actually saved it as a String. I believe I had that value in there as some old code in which I did save it as a String. I deleted my entries in the database and tried it again and it works just fine. Thank you to those who attempted to answer my question.
2017 example
It may save someone some typing, here's some completely typical code to observe a simple number value. Let's say it's a fraction called SomeFraction.
As always, Firebase rocks :)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
dev_listenForLiveFraction()
}
var r1: DatabaseReference? = nil
func dev_listenForLiveFraction() {
let p1 = "something/someFractionForExample"
r1 = Database.database().reference(withPath: p1)
r1!.observe(.value) { (snapshot) in
let sf: NSNumber = snapshot.value as? NSNumber ?? 0.666
self.example_handleSomeFraction(sf)
}
}
func example_handleSomeFraction(_ sf: NSNumber) {
print("the fraction is now \(sf)")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
clearObservations()
}
private func clearObservations() {
if r1 != nil {
r1?.removeAllObservers()
r1 = nil
}
}
Related
Currently I am fetching data from firebase, turning the data into an array of objects and trying to have that array count be the count of the "numberOfRowsInSection".
So, I get the right count on the view controller where the array is declare but I keep getting 0's when I pass that data to the tableview controller.
Below I am showing the viewcontroller where I am fetching data and putting it in a filtered array:
func fetchAllData( completion: #escaping (_ call: [Restaurant]) -> Void) {
self.ref = Database.database().reference()
self.ref?.observe(.value, with: { (snap) in
guard let topArray = snap.value as? [[String:Any]] else {print(":(") ; return }
var restaurantArray = [Restaurant]()
for dictionary in topArray {
self.restaurantGroup.enter()
guard let address = dictionary["Address"] as? String,
let city = dictionary["City"] as? String,
let inspectionDate = dictionary["Inspection Date"] as? String,
let name = dictionary["Name"] as? String,
let major = dictionary["Number of Occurrences (Critical Violations)"] as? Int,
let minor = dictionary["Number of Occurrences (Noncritical Violations)"] as? Int,
let violationTitle = dictionary["Violation Title"] as? String else { continue }
print(2)
//MARK: - creates restaurants from the list above
let restaurant = Restaurant(address: address, city: city, inspectionDate: inspectionDate, name: name, major: major, minor: minor, violationTitle: violationTitle)
print(3)
//MARK: - Adds a restaurant to restaurant array instance
restaurantArray.append(restaurant)
}
self.restaurantList = restaurantArray
self.restaurantGroup.leave()
completion(self.restaurantList)
let dictionaryNew = Dictionary(grouping: self.restaurantList) { $0.name + " " + $0.address}
self.restaurantListModified = dictionaryNew
print(self.restaurantListModified.count)
})
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
var filteredArray = [[Restaurant]]()
guard let userSearch = searchBar.text?.uppercased() else { return }
pulleyViewController?.setDrawerPosition(position: .partiallyRevealed, animated: true)
var nameArray = [String]()
for (key, value) in restaurantListModified {
if value[0].name.hasPrefix(userSearch.uppercased()){
filteredArray.append(value)
}
}
for subarray in filteredArray {
let nameArrayForTBView = subarray[0].name
nameArray.append(nameArrayForTBView)
}
self.tableViewNameArray = nameArray
print("\(tableViewNameArray.count)ππππππππππππ")
print("this First")
}
}
The tableViewNameArray is the array I am trying to pass to this tableView Controller
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("This second")
print(MapViewController.sharedMapViewController.tableViewNameArray.count)
return MapViewController.sharedMapViewController.tableViewNameArray.count
}
I also have my array set to a Notification Center that is received in the tableView Controller as shown below:
var tableViewNameArray = [String]() {
didSet {
DispatchQueue.main.async {
DispatchQueue.main.async {
NotificationCenter.default.post(name: MapViewController.RestaurantNotification.notificationSet, object: self)
}
}
}
}
Points:
My array from viewcontroller is getting the right count on the view controller but the accurate count is not being passed to the tableView Controller.
the tableview count for the array being passed is 0
I found the problem that #Kamran pointed to. I was not assigning self.tableViewNameArray = nameArray. So I changed
var tableViewNameArray to
static var tableViewNameArray
If you're new to programming like I, make sure you read a bit more on local and global variables. Very helpful.
I have a json database on firebase and trying to get them and put into local array of dictionaries.
My json model on Firebase
My struct model is also like below
struct Places {
var type:String!
var country:String!
var name:String!
var image:String!
var coords:[Coords]!
init(type: String, country: String, name: String, image: String, coords: [Coords]) {
self.type = type
self.country = country
self.name = name
self.image = image
self.coords = coords
}
init(snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as! [String:Any]
type = snapshotValue["type"] as! String
country = snapshotValue["country"] as! String
name = snapshotValue["name"] as! String
image = snapshotValue["image"] as! String
coords = snapshotValue["coords"] as! [Coords]!
}
}
And also [Coords] struct model like below:
struct Coords {
var latStart:String!
var latEnd:String!
var lonStart:String!
var lonEnd:String!
init(latStart: String, latEnd: String, lonStart: String, lonEnd: String) {
self.latStart = latStart
self.latEnd = latEnd
self.lonStart = lonStart
self.lonEnd = lonEnd
}
}
And I am trying to get and put json data by below code:
placesRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
print("data not exist")
return
}
var plc: [Places] = []
for eachPlace in (snapshot.children){
let place = Places(snapshot: eachPlace as! FIRDataSnapshot)
plc.append(place)
}
self.allPlaces = plc
The problem is that I can get the array of dictionary except coords dictionary inside array of dictionary. [Coords] dictionary seems null and I would like to know what the problem is. Thanks for any suggestion.
Because snapshotValue["coords"] as! [Coords]! are not Coords yet. They are just dictionaries. You have to go through each dictionary in snapshotValue[βcoordsβ] and init a Coords object, then when youβre finished group them all into an array and assign it to self.coords of the Places struct. The map function is really convenient for this.
Example:
I would change the Coords init function to something like:
init(dictionary: [String : AnyObject]) {
self.latStart = dictionary["lat1"] as? String
//...
//...
}
Then in Places init use something like:
coords = (snapshotValue["coords"] as? [[String : AnyObject]])?.map({ Coord(dictionary: $0) })
I didn't test this and making some assumptions here, but you should be able to make something similar work.
I'm trying to get access to the values stored in firebase dashboard to use them in different functions and methods in the class.
I used this method in this question
I have tried to print their values, the whole app has crashed and it gave me that their nil!
They are not nil actually!
I used a similar method in viewDidLoad and I could retrieve the values to labels!
let refer = FIRDatabase.database().reference().child("UserDevices")
var globalEmail : String!
var globalPhone : String!
var globalImageUrl: String!
override func viewWillAppear(_ animated : Bool){
super.viewWillAppear(animated)
retrieveUserData{(email,phone,ImageUrl) in
self.globalEmail = email
self.globalPhone = phone
self.globalImageUrl = ImageUrl
}
}
func retrieveUserData(_ completionBlock : #escaping ((_ email : String?, _ phone : String?, _ ImageUrl: String?)->Void)){
refer.child(byAppendingPath: self.strUserid as String).observe(.value , with: {snapshot in
if let userDict = snapshot.value as? [String:AnyObject] {
completionBlock(userDict["email"] as! String, userDict["phone"] as! String, userDict["ImageUrl"] as! String)
}
})
}
var strUserid : NSString!
override func viewDidLoad() {
super.viewDidLoad()
print(globalEmail)
print(globalImageUrl)
print(globalPhone)
self.navigationController?.navigationBar.tintColor = UIColor.white
print("idis \(self.strUserid)")
let ref = FIRDatabase.database().reference().child("UserDevices")
self.navigationController?.navigationBar.tintColor = UIColor.white
ref.child(byAppendingPath: self.strUserid as String).observe(.value, with: { snapshot in
if let dict = snapshot.value as? NSMutableDictionary{
print("dict is \(dict)")
if let Provider = dict["name"] as? String
{
self.DeviceDetailsProvider.text = Provider
// self.navigationItem.title = Provider
}
if let name = dict["DeviceName"] as? String
{
self.DeviceDetailsName.text = name
self.navigationItem.title = name
}
if let ShortDescription = dict["Description"] as? String
{
self.DeviceDetailsDescription.text = ShortDescription
}
if let City = dict["city"] as? String
{
self.DeviceDetailsCity.text = City
}
}
})
self.DeviceDetailsImageView.downloadedFrom(link: globalImageUrl)
}
Why I'm getting a crash here!
Change ref.child(byAppendingPath: self.strUserid as String)
To:-
ref.child(self.strUserid)
Also remove let refer = FIRDatabase.database().reference().child("UserDevices").
You can not initialise your reference globally outside a scope because you don't know in which order your classes are being initialised, and probable scenario is that your FIRDatabase hasn't even been initialised yet when you try to initialise let refer.
Instead of refer in retrieveUserData use
FIRDatabase.database().reference().child("UserDevices")
You see in a viewController's LIFE CYCLE, viewdidLoad is called before than viewWillAppear:
So what you need is:-
override func viewDidLoad() {
super.viewDidLoad()
..
retrieveUserData{(email,phone,ImageUrl) in
self.globalEmail = email
self.globalPhone = phone
self.globalImageUrl = ImageUrl
self.DeviceDetailsImageView.downloadedFrom(link: globalImageUrl)
// .. Do your stuff...
}
}
Read: Viewcontroller's Lifecycle
I am trying to retrieve the data from firebase database. However, I cannot get my local variables assigned to the values of the database. I am using the following classes and methods.
class Episode {
var title: String?
var description: String?
var location: String?
var discount: String?
var star: Int?
init() {
self.title = ""
self.description = ""
self.location = ""
self.discount = ""
self.star = 0
}
This is my method for pulling the data from the databse
func getValues() -> Episode {
let rootRef = FIRDatabase.database().reference().child("Restaurants").child("The Kafe")
let descriptionRef = rootRef.child("Description")
let discountRef = rootRef.child("Discount")
let locationRef = rootRef.child("Location")
let starRef = rootRef.child("Star")
let episode = Episode()
descriptionRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.description = snap.value as? String
}
discountRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.discount = snap.value as? String
}
locationRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.location = snap.value as? String
}
starRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
episode.star = snap.value as? Int
print(episode.description!)
}
return episode
}
When I print out the values of the returned episode, they are all empty. However, when I print the values within the closure itself (Eg. if I do print(episode.description) within the obserEventType closure, it works fine. But if I print it outside it is empty.
I think I am missing something fundamental about swift or firebase. I am new to iOS programming so any help would be greatly appreciated.
Only inside the first observer you will have the value the return will always be nil, that is because only the return is trying to work in a sync way while firebase will always work in an async way
rootRef.observeEventType(.Value, withBlock: {(snap) in
let ep: Dictionary<String,AnyObject?> = [
"title": snap.childSnapshotForPath("Title").value as? String,
"description": snap.childSnapshotForPath("Description").value as? String,
"location": snap.childSnapshotForPath("Location").value as? String,
"discount": snap.childSnapshotForPath("Discount").value as? String,
"star": (snap.childSnapshotForPath("Star").value as? NSNumber)?.integerValue,
]
//Here you have your data in your object
episode = Episode(key: snap.key, dictionary: ep)
})
rootRef.observeEventType(.Value) { (snap: FIRDataSnapshot) in
print(snap.childSnapshotForPath("Title").value as? String)
}
return episode!
Also if you want to get it from a function like that you should probably use observeSingleEventType.
You need to rethink flow of your code because you are expecting firebase to work synchronously when it is always asynchronous. The way you have your getValues function will never work.
To solve this issue you should read about async execution and callbacks in swift.
All Firebase events are asynchronous so they are executed in a non-sequential way, that is why you only have access to the data inside the context of the callback...if you put a print outside the callback it is executed in a synchronous way so it gets executed before the callback, that is why it is in its initial status
1) You only need the rootRef, delete the rest
let ref = FIRDatabase.database().reference().child("Restaurants").child("The Kafe")
2) You only need one observer
var episode:Episode? = nil
rootRef.observeEventType(.Value,withBlock: {(snap) in
let ep:Dictionary<String,AnyObject?> = [
"title":snap.childSnapshotForPath("title").value as? String,
//Etc...
"star":(snap.childSnapshotForPath("price").value as? NSNumber)?.integerValue,
]
//Here you have your data in your object
episode = Episode(key:snap.key,dictionary:ep)
}
3) your episode class can be like this
class Episode {
private var _key:String!
private var _title:String?
//Etc.....
private var _star:Int?
var key:String!{
return _key
}
var title:String?{
return _title
}
//Etc....
var star:Int?{
return _star
}
init(key:String!, title:String?,//etc...., star:Int?){
self._key = key
self._title = title
//Etc....
}
init(key:String,dictionary:Dictionary<String,AnyObject?>){
_key = key
if let title = dictionary["title"] as? String{
self._title = title
}
//Etc...
if let star = dictionary["star"] as? Int{
self._star = star
}
..
}
}
I am working on a Firebase Swift project using CocoaPods.
Every time after I log in the main ViewController, automatically I get EXC_BREAKPOINT error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Here are some of my code lines where I got errors:
All codes from Joke.swift:
import Foundation
import Firebase
class Joke {
private var _jokeRef: Firebase!
private var _jokeKey: String!
private var _jokeText: String!
private var _jokeVotes: Int!
private var _username: String!
var jokeKey: String {
return _jokeKey
}
var jokeText: String {
return _jokeText
}
var jokeVotes: Int {
return _jokeVotes //1
}
var username: String {
return _username
}
// Initialize the new Joke
init(key: String, dictionary: Dictionary<String, AnyObject>) {
self._jokeKey = key
// Within the Joke, or Key, the following properties are children
if let votes = dictionary["votes"] as? Int {
self._jokeVotes = votes
}
if let joke = dictionary["jokeText"] as? String {
self._jokeText = joke
}
if let user = dictionary["author"] as? String {
self._username = user
} else {
self._username = ""
}
// The above properties are assigned to their key.
self._jokeRef = DataService.dataService.JOKE_REF.childByAppendingPath(self._jokeKey)
}
// Add or Subtract a Vote from the Joke.
func addSubtractVote(addVote: Bool) {
if addVote {
_jokeVotes = _jokeVotes + 1
} else {
_jokeVotes = _jokeVotes - 1
}
// Save the new vote total.
_jokeRef.childByAppendingPath("votes").setValue(_jokeVotes)
}
}
In JokeCellTableViewCell.swift:
var joke: Joke!
...............
func configureCell(joke: Joke) {
self.joke = joke
// Set the labels and textView.
self.jokeText.text = joke.jokeText
self.totalVotesLabel.text = "Total Votes: \(joke.jokeVotes)" // 2
self.usernameLabel.text = joke.username
// Set "votes" as a child of the current user in Firebase and save the joke's key in votes as a boolean.
.........
}
And in the main ViewController, JokesFeedTableViewController.swift:
var jokes = [Joke]()
....................
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let joke = jokes[indexPath.row]
// We are using a custom cell.
if let cell = tableView.dequeueReusableCellWithIdentifier("JokeCellTableViewCell") as? JokeCellTableViewCell {
// Send the single joke to configureCell() in JokeCellTableViewCell.
cell.configureCell(joke) // 3
return cell
} else {
return JokeCellTableViewCell()
}
...........
// 1, // 2, // 3 are code lines where errors appear.
I hope you could help me to fix this!
Thank you in advance!
Your problem is that you have not clearly defined the expectations of the Joke class.
Your initializer suggests that the properties on Joke should be optional, however, you are using them as though they are not. You must decide on which way you want to take it.
If the properties can be optional, I would suggest something like this:
class Joke {
private let jokeReference: Firebase
let jokeKey: String
private(set) var jokeText: String?
private(set) var jokeVotes: Int?
let username: String
// Initialize the new Joke
init(key: String, dictionary: Dictionary<String, AnyObject>) {
jokeKey = key
// Within the Joke, or Key, the following properties are children
if let votes = dictionary["votes"] as? Int {
jokeVotes = votes
}
if let joke = dictionary["jokeText"] as? String {
jokeText = joke
}
if let user = dictionary["author"] as? String {
username = user
} else {
username = ""
}
// The above properties are assigned to their key.
jokeReference = DataService.dataService.JOKE_REF.childByAppendingPath(jokeKey)
}
}
However, if the properties should never be nil, you need something like this:
class Joke {
private let jokeReference: Firebase
let jokeKey: String
let jokeText: String
let jokeVotes: Int?
let username: String
// Initialize the new Joke
init?(key: String, dictionary: Dictionary<String, AnyObject>) {
jokeKey = key
guard let votes = dictionary["votes"] as? Int,
joke = dictionary["jokeText"] as? String else {
return nil
}
jokeText = joke
jokeVotes = votes
if let user = dictionary["author"] as? String {
username = user
} else {
username = ""
}
// The above properties are assigned to their key.
jokeReference = DataService.dataService.JOKE_REF.childByAppendingPath(jokeKey)
}
}