Why is my variable empty outside of a code block? - ios

Alright so here is my code:
var locationCount: Int!
var latLocation = [Double]()
var longLocation = [Double]()
func polyline() {
var coords = [CLLocationCoordinate2D]()
databaseHandle = databaseRef.child("RunList").child(runName).child("locations").observe(.value, with: { (snapshot) in
self.locationCount = Int(snapshot.childrenCount)
print(self.locationCount)
func getLocations() {
var i = 0
while i < self.locationCount {
self.databaseHandle = self.databaseRef.child("RunList").child(self.runName).child("locations").child("\(i)").observe(.value, with: { (snapshot) in
let locData = snapshot.value as? [String: AnyObject]
let lat = (locData?["lat"] as? Double)!
let long = (locData?["long"] as? Double)!
self.latLocation.append(lat)
self.longLocation.append(long)
})
i = i + 1
}
}
getLocations()
})
}
So the problem I am facing is that when I try to call the two arrays latLocation and longLocation outside of the code blocks/polyline(), they return as empty. So for example, if I try to print them in viewDidLoad(), they both print as empty arrays. How can I fix this? The same issue is with locationCount too, if I print that outside of the code blocks then I get an error where the value is nil because it has no value, but inside of the code block it works perfectly fine. This is really confusing me and I think there is a simple solution to it that I have overlooked.

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.

Issue pulling content from Firebase into Swift

I'm trying to get quotes out of Firebase, and I'm struggling. Granted, I also have no idea what I'm doing. I could use some help!
In Firebase, my quotes are set up like :
root
--> quotes
--> quoteID
--> quoteText, quoteAttribution
I'm trying to pull quotes down from Firebase, add them to a local array (to later put in a dictionary), and then pull a random one to use in the app. I hope to put the quoteText into quoteLabel.text, and the quoteAttribution into authorLabel.text. I found this solution in another StackOverflow issue, but it throws the following error at line 43:
Could not cast value of type 'NSNull' (0x10f549740) to 'NSDictionary' (0x10f549178).
2018-07-21 22:49:50.241473-0400 Wavefully[72475:1126119] Could not cast value of type 'NSNull' (0x10f549740) to 'NSDictionary' (0x10f549178).
Does anyone have any tips for how I might pull quoteText and quoteAttribution out of Firebase to use in my app?
Here's my code:
class ViewController: UIViewController {
class quoteClass {
var uid = ""
var quote = ""
var author = ""
}
#IBOutlet weak var quoteLabel: UILabel!
#IBOutlet weak var authorLabel: UILabel!
var ref: DatabaseReference?
var databaseHandler: DatabaseHandle?
var quotesArray = [quoteClass]()
override func viewDidLoad() {
super.viewDidLoad()
// Set the reference to Firebase
ref = Database.database().reference()
let quotesRef = ref?.child("quotes")
quotesRef?.observeSingleEvent(of: .value, with: { (snapshot) in
for _ in snapshot.children {
let quoteSnap = snapshot
let quoteKey = quoteSnap.key
let thisQuoteRef = quotesRef?.child("quoteID")
thisQuoteRef?.observeSingleEvent(of: .value, with: { (quoteSnap) in
let singlequoteSnap = quoteSnap
let quoteDict = singlequoteSnap.value as! [String:AnyObject]
let quote = quoteDict["quoteText"]
let author = quoteDict["quoteAttribution"]
let aQuote = quoteClass()
aQuote.uid = quoteKey
aQuote.quote = quote as! String
aQuote.author = author as! String
print(aQuote.quote)
print(aQuote.author)
print(aQuote.uid)
self.quotesArray.append(aQuote)
})
}
})
let singleQuote = quotesArray.randomItem()!
print(singleQuote.uid)
print(singleQuote.quote)
print(singleQuote.author)}}
Thanks a ton for helping!
Alternatively you can also use your data by casting it into NSDictionary like below:
let dictionary = snapshot.value as? NSDictionary
let quote = dictionary["quoteText"] as? String ?? ""
Okay, so I was making it way harder than it needed to be. I did this, and it's working:
func grabData() {
ref = Database.database().reference()
ref?.child("quotes").observe(.value, with: {
snapshot in
for snap in snapshot.children.allObjects as! [DataSnapshot] {
guard let dictionary = snap.value as? [String:AnyObject] else {
return
}
let quote = dictionary["quoteText"] as? String
let author = dictionary["quoteAttribution"] as? String
let id = dictionary["quoteID"] as? String
self.quoteLabel.text = quote
self.authorLabel.text = author
print(quote!)
print(author!)
print(id!)
}
})
}
Now, I just have to call grabData() in my viewDidLoad to get a quote. Up next: randomize what quote is shown. Oh, and probably store it in Core Data or Realm for local storage. 🤓
Thanks for stopping by!

How to read data from FireBase

I have problem reading from Firebase in Swift.
Here is my Firebase database:
and here is my code:
var ref: FIRDatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
NSLog("Reading from DB")
ref = FIRDatabase.database().reference()
self.ref?.child("Frais").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? [String: Int]
var frpx1 = (value?["frpx1"]!)!
var frpx10 = (value?["frpx10"]!)!
var frpx11 = (value?["frpx11"]!)!
var frpx12 = (value?["frpx12"]!)!
var frpx13 = (value?["frpx13"]!)!
var frpx14 = (value?["frpx14"]!)!
var frpx15 = (value?["frpx15"]!)!
var frpx16 = (value?["frpx16"]!)!
})
print(frpx1)
print(frpx10)
print(frpx11)
print(frpx12)
print(frpx13)
print(frpx14)
print(frpx15)
print(frpx16)
}
I did not find the problem.
I do not have the data from database in frpx1, ..., frpx16.
Your code have a couple of minor issues:
you are casting the returned value to [String: Int] when you should be using [String: Any] instead since not all values are String based.
you are printing the results outside the completion handler. You need to wait the handler to be called to then read the results (i.e., when the method observeSingleEvent returns Firebase is still processing your request).
Fixing both issues should get you going:
...
self.ref?.child("Frais").observeSingleEvent(of: .value, with: {
(snapshot) in
guard let value = snapshot.value as? [String: Any] else {
print("Snapshot type mismatch: \(snapshot.key)")
return
}
let frpx1 = value["frpx1"]
let frpx10 = value["frpx10"]
let frpx11 = value["frpx11"]
...
print(frpx1)
print(frpx10)
print(frpx11)
...
})
PS. I also improved your coding style a little to help prevent further issues ;)

Can't modify array because of for loop in swift?

I've been struggling with this all day, it doesn't seem to make any sense because I have very similar code that is working fine. I've tried everything, I've tried making a separate method that returns a string array, but none of it has worked. Every time, the postIDs array is set to null when accessed outside of the bracket followed by the parentheses (after the line reading "print(self.postIDs)"). Thanks for any help you could give me.
var postIDs = [String]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
let ref = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
ref.child("users").child(uid).child("saved").observeSingleEvent(of: .value, with: { snapshot in
var ids = [String]()
let saved = snapshot.value as! [String:AnyObject]
for (elem, _) in saved {
ids.append(elem)
}
self.postIDs = ids
print(self.postIDs) // returns the values I would expect
})
ref.removeAllObservers()
guard self.postIDs.count >= 1 else {return} // postIDs count is equal to 0 here, and when I print postIDs the result is []
It is because
ref.child("users").child(uid).child("saved").observeSingleEvent(of: .value, with: { snapshot in
var ids = [String]()
let saved = snapshot.value as! [String:AnyObject]
for (elem, _) in saved {
ids.append(elem)
}
self.postIDs = ids
print(self.postIDs) // returns the values I would expect
})
works on background and other line of code executes before the callback came
Check the following code
override func viewDidLoad() {
super.viewDidLoad()
usersTableView.dataSource = self
usersTableView.delegate = self
// getSnapShot()
let databaseRef = Database.database().reference()
databaseRef.child("Users").observe(.value, with: { (snapshot) in
if snapshot.exists() {
self.postData = snapshot.value! as! [String : AnyObject]
self.postData.removeValue(forKey: self.appDelegate.KeyValue)
// self.getUserNames(Snapshot: self.postData)
}
else{
print("No users")
}
print(self.postData) //Does not return nil
self.getSnapShot() //Take snapshot outside paranthesis
})
print(self.postData) //Returns nil
}
func getSnapShot() {
print(self.postData) //Value of Snapshot is printed
}

How to retrieve firebase database properly?

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

Resources