Create an Array of Objects with Firebase Async Dictionary Download (Swift) - ios

I'm new to Swift. I have been having trouble downloading Firebase dictionaries and turning them into an array of objects.
What am I doing wrong with the syntax below? I've spent the last two days unsuccessfully trying to figure this out. The following gives me an index out of range error. Is this because the Firebase Dictionary hasn't finished downloading yet or is my for in loop sytax flawed? Perhaps both? Thanks.
// Array of Location Objects
var locationsArray:[Location] = [Location]()
var ref = Firebase(url: "<MYFIREBASEURL>")
var dictionaryOfRecommendations:[NSDictionary] = [NSDictionary]()
var currentlyConstructingLocation:Location = Location()
func getLocationData() {
let titleRef = self.ref.childByAppendingPath("events")
titleRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
var tempDict = [NSDictionary]()
for item in snapshot.children {
let child = item as! FDataSnapshot
let dict = child.value as! NSDictionary
tempDict.append(dict)
}
self.dictionaryOfRecommendations = tempDict
})
// Parse data from Firebase
// Loop through each dictionary and assign values to location object
var index:Int
for index in 0...dictionaryOfRecommendations.count {
// Current Json dictionary
let jsonDictionary:NSDictionary = self.dictionaryOfRecommendations[index]
self.currentlyConstructingLocation.title = jsonDictionary["title"] as! String!
self.currentlyConstructingLocation.locationsLatitudeArray = jsonDictionary["latitude"] as! Double
self.currentlyConstructingLocation.locationsLongitudeArray = jsonDictionary["longitude"] as! Double
// Append to Locations Array and start new Location
self.locationsArray.append(currentlyConstructingLocation)
self.currentlyConstructingLocation = Location()
}
// Notify the MainViewController that the Locations are ready.
...
}

Here's the updated correct code for the question above based on Jay's helpful guidance:
// Model to download location data for events.
//Firebase reference
var ref = Firebase(url: "<MYFIREBASEURL")
var locationsArray:[Location] = [Location]()
var dictionaryOfRecommendations:[NSDictionary] = [NSDictionary]()
var currentlyConstructingLocation:Location = Location()
func getLocationData() {
let titleRef = self.ref.childByAppendingPath("events")
titleRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
var tempDict = [NSDictionary]()
for item in snapshot.children {
let child = item as! FDataSnapshot
let dict = child.value as! NSDictionary
tempDict.append(dict)
}
self.dictionaryOfRecommendations = tempDict
self.ParseFirebaseData()
})
}
func ParseFirebaseData() {
// Parse data from Firebase
// Loop through each dictionary and assign values to location object
var index:Int
for index in 0...dictionaryOfRecommendations.count - 1 {
// Current Json dictionary
let jsonDictionary:NSDictionary = self.dictionaryOfRecommendations[index]
self.currentlyConstructingLocation.title = jsonDictionary["title"] as! String!
self.currentlyConstructingLocation.locationsLatitudeArray = jsonDictionary["latitude"] as! Double
self.currentlyConstructingLocation.locationsLongitudeArray = jsonDictionary["longitude"] as! Double
// Append to Locations Array and start new Location
self.locationsArray.append(currentlyConstructingLocation)
self.currentlyConstructingLocation = Location()
}
}

Related

Two functions that get data from a FireBase database, and a third function that performs some calulations

I have two functions that successfully retrieve integers from Firebase. I'd like a third function that does some simple subtraction from the integers gathered in the first two functions.
However, I'm very new to this, so can't get it to work correctly.
The output of the two functions that gather data from Firebase are:
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
and
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
What I'd like is a third function that looks like this:
let pointsBalance = sumOfPointsCompleted - pointsRedeemedAsInt
However, the third function doesn't recognise sumOfPointsCompleted, nor pointsRedeemedAsInt.
// First Function:
func loadPointsRedeemed() {
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
}
)}
//Second Function:
func LoadPointsCompleted() {
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
}
}
}
)}
// Third Function (which does not work):
func BalanceOfPoints(){
let balance = sum - pointsRedeemedAsInt
}
The error is:
Use of unresolved identifiers sum and pointsRedeemedAsInt
Furthermore, how do I ensure that everything is executed in the right order? ie, the loadPointsCompleted function must run (and complete) first, followed by the loadPointsRedeemed function, and finally the BalanceOfPoints function.
Actually, the problem is that you are not considering that retrieving data from remote sources is asynchronous.
This means that you have to wait for data to be retrieved before calling the other functions.
To achieve this result, you should use swift closure (callback in other languages) with completion handler. Check this documentation.
Change your functions this way:
First Function
func loadPointsRedeemed(completion: #escaping (_:Int)->()){
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
//Call your return back function called "completion"
completion(pointsRedeemedAsInt)
}
)}
Second Function
func loadPointsCompleted(completion: #escaping (_:Int)->()){
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
}
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
completion(sumOfPointsCompleted)
}
}
)}
Third Function
func balanceOfPoints(completion: #escaping (_:Int)->()) {
loadPointsCompleted{(sum) in
//HERE YOU CAN USE THE RESULT OF loadPointsCompleted
//I CALLED IT sum
loadPointsRedeemed{ (pointsRedeemedAsInt) in
// HERE YOU CAN USE THE RESULT OF loadPointsRedeemed
//I CALLED IT pointsRedeemedAsInt
let balance = sum - pointsRedeemedAsInt
completion(balance)
}
}
}
To call the balance function wherever you want:
balanceOfPoints{ (balance) in
// Whatever you want with balance
}
If you change the view ( for example you set some label text ), be sure to use the functions in the main thread.
The problem is that you are trying to access variables outside the scope of BalanceOfPoints().
Try returning the values you want to use in the equation from the first two functions, loadPointsRedeemed() and LoadPointsCompleted(). This can be done like so:
First Function
func loadPointsRedeemed() -> Int {
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
return pointsRedeemedAsInt
}
)}
Second Function
func loadPointsCompleted() -> Int {
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
}
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
return sumOfPointsCompleted
}
}
)}
Third Function
func balanceOfPoints() -> Int {
let sum = loadPointsCompleted()
let pointsRedeemedAsInt = loadPointsRedeemed()
let balance = sum - pointsRedeemedAsInt
return balance
}
Now, wherever you call the functions loadPointsRedeemed() and loadPointsCompleted(), replace these calls with balanceOfPoints.
Notice the main changes I made to your code are adding return values to your functions so they can be used in other areas of your code. Check out the Swift Functions Documentation to learn more.

How to append data in to NSMutableArray in ios swift

I'm working on an food ordering app currently when I add items into any array, then I need to pass that items to cartVC via CartDataModal where the cartArrayDict is of NSMutableArray type. But when I insert the SelectedDict the app crashes can anyone help me with this?
Heres my code:
var restMenu = [[String:Any]]()
func addTapped(cell: RestaurantItemViewCell)
{
//get the indexPath for add button click
let indexPath = self.restTableView.indexPath(for: cell)
print("the indexPath is", indexPath?.row)
print("all obj is",restMenu)
var selectedDict = restMenu[(indexPath?.row)!]
print("selected dict is",selectedDict)
selectedDict["ItemQuant"] = cell.itemQuantityLabel.text
print("selected dict is",selectedDict)
// Append In Cart Modal
CartDataModal.shared_Inst.cartArrayDict.insert(selectedDict, at: (indexPath?.row)!)
print("rest menu is",restMenu)
restMenu.remove(at: (indexPath?.row)!)
print("rest menu is",restMenu)
restMenu.insert(selectedDict, at: (indexPath?.row)!)
print("restmenu is",restMenu)
}
My CartDataModal:
class CartDataModal: NSObject {
static let shared_Inst = CartDataModal()
var cartArrayDict: [String:Any]!
}
from my ViewController i'm getting data From my Restaurant.plist
func moveToDetailController(img:UIImage,name:String)
{
print("the rest name is", name)
let pathUrl = Bundle.main.path(forResource: "ResturantFile", ofType: ".plist")
print("path url is",pathUrl as Any)
let finalArray = NSMutableArray(contentsOfFile: pathUrl!)
print("final Array is",finalArray)
let restaurantNames = finalArray?.firstObject as? NSDictionary
print("resturant name is",restaurantNames as Any)
if let menuDataArray:[[String:Any]] = restaurantNames?.value(forKey: name) as? [[String:Any]]
{
print("menu data is",menuDataArray)
let restVC = self.storyboard?.instantiateViewController(withIdentifier: "RestaurantViewController") as! RestaurantViewController
restVC.tempImg = img
restVC.tempTitle = name
restVC.restMenu = menuDataArray
self.navigationController?.pushViewController(restVC, animated: true)
}
}
You need to modify your struct:
class CartDataModal: NSObject {
static let shared_Inst = CartDataModal()
var cartArrayDict = [[String: Any]]()
}
You need to init array of the dictionary before insert. So You can init in struct or you can init before use according to your use.

Retrieve array of dictionary from Firebase & Swift 3

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.

Converting all Realm Objects to Dictionary at once

I am using Realm and I have an extension that I use to convert my Realm model into a Dictionary , but I do not know how to convert all my Realm models at once. I want to know how do I convert all the realm Objects at once and in one place, so that I can send that dictionary to a API.
Here are my Realm Object Models and the extension I use:
class OrderItemList: Object {
dynamic var dateCreated = NSDate()
let orderItems = List<OrderItem>()
}
class OrderItem: Object {
dynamic var name = " "
dynamic var amount = 0
dynamic var internalUnique = Int()
dynamic var isCompleted = false
}
Extension:
extension Object {
func toDictionary() -> NSDictionary {
let properties = self.objectSchema.properties.map { $0.name }
let dictionary = self.dictionaryWithValuesForKeys(properties)
let mutabledic = NSMutableDictionary()
mutabledic.setValuesForKeysWithDictionary(dictionary)
for prop in self.objectSchema.properties as [Property]! {
// find lists
if let nestedObject = self[prop.name] as? Object {
mutabledic.setValue(nestedObject.toDictionary(), forKey: prop.name)
} else if let nestedListObject = self[prop.name] as? ListBase {
var objects = [AnyObject]()
for index in 0..<nestedListObject._rlmArray.count {
let object = nestedListObject._rlmArray[index] as AnyObject
objects.append(object.toDictionary())
}
mutabledic.setObject(objects, forKey: prop.name)
}
}
return mutabledic
}
}
Unfortunately, there's no magic bullet for converting a batch of Realm objects to a dictionary. You'll need to query for the objects you want, and then loop through each one to produce a serialized version of it.
let realm = try! Realm()
var objectDictionaries = [NSDictionary]()
let allObjects = realm.objects(OrderItemList.self)
for object in allObjects {
let dictionary = object.toDictionary()
objectDictionaries.append(dictionary)
}
I hope that answered your question!

How to properly return an array from a function that gets a list of elements from a Firebase database with Swift

I defined the following function to get a list of zipcodes from a Firebase database in a class called Zipcodes
// Return an array of Zipcodes from the Firebase database
func getZipcodesArray(array: [String]) -> [String] {
var zipcodes = array
_ZIPCODE_REF.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
print("Zipcode: \(rest.value.stringValue)")
zipcodes.append(rest.value.stringValue)
}
})
return zipcodes.sort()
}
However, when I try to use the returned array as follows:
var arr = [String]()
var zipcodes = Zipcode.service.getZipcodesArray(arr).copy()
I get the following error:
Instance member 'arr' cannot be used on type ViewController
Zipcode.swift code:
import Foundation
import Firebase
class Zipcode {
static let service = Zipcode()
private var _BASE_REF = Firebase(url: "\(BASE_URL)")
private var _ZIPCODE_REF = Firebase(url: "\(BASE_URL)/zipcode")
var zipcodes = [String]()
var BASE_REF: Firebase {
return _BASE_REF
}
var USER_REF: Firebase {
return _ZIPCODE_REF
}
// Return an array of Zipcodes from the Firebase database
func getZipcodesArray() -> [String] {
_ZIPCODE_REF.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
print("Zipcode: \(rest.value.stringValue)")
self.zipcodes.append(rest.value.stringValue)
}
})
return zipcodes.sort()
}
}
UPDATE: added completion handler to the function
How do I return the array?
// Return an array of Zipcodes from the Firebase database
func getZipcodesArray(completionHandler: ([String]) -> Void) {
_ZIPCODE_REF.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
print("Zipcode: \(rest.value.stringValue)")
self.zipcodes.append(rest.value.stringValue)
}
})
completionHandler(self.zipcodes)
}

Resources