I am trying to run the following code, but for some reason the snapshot is never running as a result of the while loop. When I add the while loop the only thing that prints is "current count is <2" because the assignment of currentCount above is skipped for some reason. Here is the code:
override func viewDidLoad() {
//below sets the default selection as pitcher
pitcher = true
batter = false
//******Needs to show the default selection as well
batterCheck.hidden = true
batterButton.tintColor = UIColor.blueColor()
pitcherButton.tintColor = UIColor.blueColor()
//below defaults to hiding the results related data
ResultsLabel.hidden = true
playName.hidden = true
pointDiff.hidden = true
var playRef = ref.child("LiveGames").child("05-25-17").child("RedSox")
playRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
self.currentCount = Int(snapshot.childrenCount)
print(snapshot.childrenCount)
print(snapshot)
print("***" + String(self.currentCount))
})
while(self.ended != true) {
if(self.currentCount < 2) {
print("current count is < 2")
} else {
playRef.child(String(self.currentCount-2)).observeSingleEventOfType(.Value, withBlock: { (snapshot2) in
var hitterRef = snapshot2.childSnapshotForPath("/Hitter")
var pitcherRef = snapshot2.childSnapshotForPath("/Pitcher")
self.batterName.text = (String(hitterRef.childSnapshotForPath("/fName")) + " " + String(hitterRef.childSnapshotForPath("/lname")))
self.pitcherName.text = (String(pitcherRef.childSnapshotForPath("/fName")) + " " + String(pitcherRef.childSnapshotForPath("/lname")))
})
self.currentCount += 1
}
}
print("labels updated")
}
However when I comment out the while loop the currentCount is set to the correct value. Any ideas on what this while loop is doing to block the firebase snapshot from running?
The observeSingleEventOfType:withBlock is called asynchronously. That means that it will be executed in another thread and not in the scope of the ViewDidLoad method.
If you stick a breakpoint outside the completion block and one inside the completion block it will become more clear.
What you need to do if you need to update the labels after the 'observeSingleEventOfType' is complete, you should move you 'update' code in a separate method and call it in the completion block of the observeSingleEventOfType method.
Example:
override func viewDidLoad() {
//Your code here...
var playRef = ref.child("LiveGames").child("05-25-17").child("RedSox")
playRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
self.currentCount = Int(snapshot.childrenCount)
self.updateLabels()
})
//Your code here...
}
func updateLabels() {
//Your update code here...
}
I've also run into this when I had not set a rule for the parent I was observing.
Related
I am making a completion handler for a function which will return a list of objects. When it return value for first time, it works well. But when any change happen into firebase database and again observe gets called, array size gets doubled up. Why it's getting doubled up?
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
And calling like this
getStadiums(){ stadiums
print(stadiums.count) // count gets doubled up after every observe call
}
The code you're using declares stadiums outside of the observer. This means any time a change is made to the value of the database reference, you're appending the data onto stadiums without clearing what was there before. Make sure to remove the data from stadiums before appending the snapshots again:
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
stadiums.removeAll() // start with an empty array
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
This line stadiumRef.observe(.value, with: { (snapshot) in ... actually adding an observer that will be called everytime your stadium data is changed.
Because you called it twice by using getStadiums(){ stadiums ..., the total observer added will be 2.
That makes the line stadiums.append(stadium) called twice in the second call.
My suggestion would be to use stadiumRef.observe() once without calling it from getStadiums().
Create a Model as below
class OrderListModel: NSObject {
var Order:String?
var Date:String?
}
Use the below code in the view controller and you should be able to see content in your tableview
func getOrdersData() {
self.orderListArr.removeAll()
let ref = Database.database().reference().child(“users”).child(user).child("Orders")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let orderObj = OrderModel()
orderObj.Order = dictionary[“Order”] as? String
orderObj.Date = dictionary[“Date”] as? String
self.orderListArr.append(orderObj)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}, withCancel: nil)
}
func ListenForChildrenAdded() {
let registerToListenTo = "YourPathHere"
ref.child(registerToListenTo).observeSingleEvent(of: .value) { (snapshot) in
let initialChildren = snapshot.childrenCount
var incrementer = 0
ref.child(registerToListenTo).observe(.childAdded, with: { (snapshot) in
incrementer += 1
print("snapshot: \(snapshot.key) #\(incrementer)")
if incrementer == initialChildren {
print("-> All children found")
} else if incrementer > initialChildren {
print("-> Child Was Added - Run Some Code Here")
}
})
}}
func updateFirebase(){
myFun = thisIsMyFunTextView.text
IAm = iAmTextView.text
var profileKey = String()
profileRef.queryOrdered(byChild: "uid").queryEqual(toValue: userID).observe(.value, with:{
snapshot in
for item in snapshot.children {
guard let data = item as? FIRDataSnapshot else { continue }
guard let dict = data.value as? [String: Any] else { continue }
guard let profileKey = dict["profileKey"] else { continue }
self.profileRef.child(profileKey as! String).child("bodyOfIAM").setValue(IAm)
self.profileRef.child(profileKey as! String).child("bodyOfThisIsMyFun").setValue(myFun)
}
})
}
#IBAction func backButtonClicked(_ sender: Any) {
updateFirebase()
DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
self.dismiss(animated: true)
})
}
myFun and IAm are successfully defined by the changes to the textviews by the user. I can't extract the childByAutoID value without triggering this for in loop that does not end once called, continuing even as a new view controller is presented. The "bodyOfThisIsMyFun" vacillates between the old value and the new value during this loop while the "bodyOfIAM" gets correctly redefined right away and stays that way like it should. How do I get the extracted new values to replace the old values here?
I needed to add this line of code at the end of the for...in statement:
self.profileRef.removeAllObservers()
I have declared requestPostArray right at the beginning of the ViewController class. I'm trying to populate the requestPostArray from the database "Request Posts" and then populate the tableView from the requestPostArray. However when I print it's size it shows up to be 0. Any help would be appreciated.
Also, the entire else statement below is inside another closure.
else {
ref.child("Request Posts").observe(.value, with: { (snapshot) in
let count = Int(snapshot.childrenCount)
// Request Post database is empty
if count == 0 {
cell.nameLabel.text = "No requests so far."
cell.userNameLabel.isHidden = true
cell.numberOfRequestsLabel.isHidden = true
}
// Request Post data is populated
else {
var requestPostArray: [RequestPost]? = []
self.ref.child("Request Posts").observe(.value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
let post = RequestPost(snapshot: child)
requestPostArray?.append(post)
}
}
else {
print("No Result")
}
})
print("RequestPostArray size = \(requestPostArray?.count ?? 90)")
cell.nameLabel.text = self.requestPostArray?[indexPath.row].name
cell.userNameLabel.text = self.requestPostArray?[indexPath.row].name
cell.numberOfRequestsLabel.text = self.requestPostArray?[indexPath.row].name
}
})
}
observe blocks are asynchronous. You're reading requestPostArray before it has been modified by your requestPostArray?.append(post) line.
Without more context it's hard to know how you want to be populating values on this cell object but if you need "Request Posts" then you have to wait until you've been able to obtain them.
this is what should happens because observe function is asynchronous and the rest of your code is synchronous, in addition if you remove the optional unwrap from requestPostArray? you will get a nil exception because the async task needs time to get executed so the compiler will execute the snyc task before it.
basically what you have to do is the following
else {
ref.child("Request Posts").observe(.value, with: { (snapshot) in
let count = Int(snapshot.childrenCount)
// Request Post database is empty
if count == 0 {
cell.nameLabel.text = "No requests so far."
cell.userNameLabel.isHidden = true
cell.numberOfRequestsLabel.isHidden = true
}
// Request Post data is populated
else {
var requestPostArray: [RequestPost]? = []
self.ref.child("Request Posts").observe(.value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
let post = RequestPost(snapshot: child)
requestPostArray?.append(post)
}
}
else {
print("No Result")
}
print("RequestPostArray size = \(requestPostArray?.count ?? 90)")
cell.nameLabel.text = self.requestPostArray?[indexPath.row].name
cell.userNameLabel.text = self.requestPostArray?[indexPath.row].name
cell.numberOfRequestsLabel.text = self.requestPostArray?[indexPath.row].name
})
}
})
}
another advice think about using singleton so you can gain the reuse of your object and you will not invoke the database several time at the same method like you are doing now.
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
} else {
if allInterestsArray.contains(searchBar.text!.lowercaseString) {
ref.child(searchBar.text!.lowercaseString).child("users")
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
print("this should be running")
print(searchBar.text!.lowercaseString)
let handle = roomsRef.observeSingleEventOfType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
print(snapshot.value)
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
print(snap)
}
}
})
}
}
else {
print("doesn't exist")
}
}
}
I'm running a query to my firebase database when my searchBar.text is equal to something in my allInterestsArray. This is being checked for on keystroke, and I'm using dispatch_after to prevent a query being sent until the user is probably done typing.
What is happening is if I have the item in my array of "Dog" and the user gets to "dog" and then types in an "s" which makes it "dogs"..the query is still being sent off for dog and then again for dogs, so I need to cancel the query up at the top of my textDidChange func I think so on each keystroke it's being canceled, and only after a second of no typing is the query being sent off.
I was thinking of using removeHandle but I don't think that's what that's meant to be used for?
Example:
If my array = ["dog","dogs","doggies"]
The user types quickly enough so there's not a full 1 second between any two letters (one second because of the dispatch_after time I set) and they type in "dogs".. the query for dog should not have gone off, only for "dogs".
This problem could be solved using a global variable called "keepSearching" as below :
EDIT : I've now also used NSTimer to give a one-second gap for "keepSearching" to be false.
var keepSearching: Bool = true
var timer = NSTimer()
func timerAction() {
keepSearching = false
}
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
}
else if(keepSearching) {
if allInterestsArray.contains(searchBar.text!.lowercaseString) {
// just in case user keeps on typing, invalidate the previous timer, before it happens.
timer.invalidate()
// schedule a new timer everytime.
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: false)
ref.child(searchBar.text!.lowercaseString).child("users")
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
print("this should be running")
print(searchBar.text!.lowercaseString)
let handle = roomsRef.observeSingleEventOfType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
print(snapshot.value)
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
print(snap)
}
}
})
}
}
else {
print("doesn't exist")
}
}
You could add a property that gets incremented every time a dispatch_after block is submitted, and check if that value corresponds to the current one. This way you'll be able to tell if you should kick-off or not the request. Here how your controller class might look like (included only the relevant pieces to this question):
class MyController: <base class, protocols> {
var requestsCounter = 0
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
// mark that a new request is made
self.requestsCounter++
// save the number in a local var to be used when the block
// actually executes
let currentCounter = self.requestsCounter
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
} else {
dispatch_after(...) { () -> Void in
// if another dispatch_after was submitted, then the value of the
// currentCounter local variable won't match the property value,
// so we should no longer proceeed
guard currentCounter == self.requestsCounter else { return }
// ... the original code here
}
}
How does it work:
the first time the delegate method is called, it increments the requestsCounter property, so now it has a value of 1
this value (1) gets saved into the local variable currentCounter, whos value is captured by the dispatch_after closure.
if the delegate method is called before dispatch_after executes the closure, then the requestsCounter will be incremented to 2, and a new currentCounter variable, with that value, will be created and captured by the 2nd dispatch_after closure
now when the first dispatch_after closure executes, it captured currentCounter value will hold 1, but self.requestsCounter will be 2, thus the closure will return without doing actual work
the same logic applies for subsequent requests, the searchBar: textDidChange: delegate method will increment every time the requestsCounter property, which in turn will invalidate previously scheduled closures, as each captures the old values of the requestsCounter property.
I'm pretty new to IOS Application Development.
I'm trying to stop viewWillAppear from finishing until after my function has finished working. How do I do that?
Here's viewWillAppear:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
checkFacts()
if reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
func checkFacts() {
let date = getDate()
var x: Bool = true
var ind: Int = 0
print("count is ", birdFacts.count)
while ind < birdFacts.count {
print("accessing each bird fact in checkFacts")
let imageAsset: CKAsset = birdFacts[ind].valueForKey("birdPicture") as! CKAsset
let image = UIImage(contentsOfFile: imageAsset.fileURL.path!)
print(image)
if image == nil {
if (birdFacts[ind].valueForKey("sortingDate") != nil){
print("replacing fact")
print("accessing the sortingDate of current fact in checkFacts")
let sdate = birdFacts[ind].valueForKey("sortingDate") as! NSNumber
replaceFact(sdate, index: ind)
}
/*else {
birdFacts.removeAll()
print("removing all bird facts")
}*/
}
ind = ind + 1
print(ind)
}
self.saveFacts()
let y = checkRepeatingFacts()
if y {
print("removing all facts")
birdFacts.removeAll()
//allprevFacts(date, olddate: 0)
}
}
checkFacts references 2 others functions, but I'm not sure they're relevant here (but I will add them in if they are and I'm mistaken)
Instead of trying to alter or halt the application's actual lifecycle, why don't you try using a closure?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
checkFacts(){ Void in
if self.reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
}
func checkFacts(block: (()->Void)? = nil) {
let date = getDate()
var x: Bool = true
var ind: Int = 0
print("count is ", birdFacts.count)
while ind < birdFacts.count {
print("accessing each bird fact in checkFacts")
let imageAsset: CKAsset = birdFacts[ind].valueForKey("birdPicture") as! CKAsset
let image = UIImage(contentsOfFile: imageAsset.fileURL.path!)
print(image)
if image == nil {
if (birdFacts[ind].valueForKey("sortingDate") != nil){
print("replacing fact")
print("accessing the sortingDate of current fact in checkFacts")
let sdate = birdFacts[ind].valueForKey("sortingDate") as! NSNumber
replaceFact(sdate, index: ind)
}
/*else {
birdFacts.removeAll()
print("removing all bird facts")
}*/
}
ind = ind + 1
print(ind)
}
self.saveFacts()
let y = checkRepeatingFacts()
if y {
print("removing all facts")
birdFacts.removeAll()
//allprevFacts(date, olddate: 0)
}
// CALL CODE IN CLOSURE LAST //
if let block = block {
block()
}
}
According to Apple Documentation:
Closures are self-contained blocks of functionality that can be passed around and used in your code.
Closures can capture and store references to any constants and variables from the context in which they are defined.
So by defining checkFacts() as: func checkFacts(block: (()->Void)? = nil){...} we can optionally pass in a block of code to be executed within the checkFacts() function.
The syntax block: (()->Void)? = nil means that we can take in a block of code that will return void, but if nothing is passed in, block will simply be nil. This allows us to call the function with or without the use of a closure.
By using:
if let block = block {
block()
}
we can safely call block(). If block comes back as nil, we pass over it and pretend like nothing happened. If block is not nil, we can execute the code contained within it, and go on our way.
One way we can pass our closure code into checkFacts() is by means of a trailing closure. A trailing closure looks like this:
checkFacts(){ Void in
if self.reset != 0 {
print("removing all bird facts")
birdFacts.removeAll()
}
}
Edit: Added syntax explanation.
So based on the comments, checkFacts is calling asynchronous iCloud operations that if they are not complete will result in null data that your view cannot manage.
Holding up viewWillAppear is not the way to manage this - that will just result in a user interface delay that will irritate your users.
Firstly, your view should be able to manage null data without crashing. Even when you solve this problem there may be other occasions when the data becomes bad and users hate crashes. So I recommend you fix that.
To fix the original problem: allow the view to load with unchecked data. Then trigger the checkData process and when it completes post an NSNotification. Make your view watch for that notification and redraw its contents when it occurs. Optionally, if you don't want your users to interact with unchecked data: disable appropriate controls and display an activity indicator until the notification occurs.