Stopping a query to firebase? - ios

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.

Related

Swift Func called after expected

I have a button setup so that it saves a CK record based on a users choice from a different part of the UI. Once the function is called the CKRecord is saved in a variable. The next operation the code should take is unwrapping that variable and using it to edit and save the CK record. The Issue is the function I call first, loadChallengeRecord(), isn't the first operation made when the button is pressed. Instead the unwrapping function is run first which is causing the program to exit the unwrap function because the record is nil, and then the loadChallengeRecord() function is called late. Here is the example:
func loadChallengeRecord() {
if let unwrapped = existingChallengeToDetails {
recordID = CKRecord.ID(recordName: unwrapped, zoneID: zone)
publicDatabase.fetch(withRecordID: recordID!) { (record, error) in
if record != nil {
self.currentChallenge = record
} else {
print("error fetching challenge record from server")
}
}
}
}
#IBAction func btnVote(_ sender: Any) {
// load record and save it to var existingChallengeToDetails
loadChallengeRecord()
if let unwrapped = existingChallengeToDetails { }// edit and save record
else { // error }
What am i doing wrong? How can i fix this? Can I denote a priority for these functions to run?
Write your function with completion handler like this
func loadChallengeRecord(completion:#escaping ()->Void) {
if let unwrapped = existingChallengeToDetails {
recordID = CKRecord.ID(recordName: unwrapped, zoneID: zone)
publicDatabase.fetch(withRecordID: recordID!) { (record, error) in
defer { completion }
if record != nil {
self.currentChallenge = record
} else {
print("error fetching challenge record from server")
}
}
}
}
Use it like this ... when it returns completion you can do other stuff dependent on this
loadChallengeRecord {
// do your stuff here
}
The easiest solution is to do the things you have to do in the (asynchronous) completion handler
func loadChallengeRecord() {
guard let unwrapped = existingChallengeToDetails else { return }
recordID = CKRecord.ID(recordName: unwrapped, zoneID: zone)
publicDatabase.fetch(withRecordID: recordID!) { (record, error) in
if let record = record {
self.currentChallenge = record
// edit and save record
} else {
print("error fetching challenge record from server", error!)
}
}
}
#IBAction func btnVote(_ sender: Any) {
// load record and save it to var existingChallengeToDetails
loadChallengeRecord()
}
The other two answers worked for the specific case, and work for many causes, but they weren't exactly what I was looking for. I kept running to similar issues and after some research I finally found what I was looking for: Semaphores
Here is a basic explanation:
func doSomething() {
let semaphore = DispatchSemaphore(value: 0) // Setup the semaphore to value:0
var x = 1
var y = 2
if y != 0 {
var sum = x + y
semaphore.signal(). // Sends signal that code is complete
// the code can continue from where semaphore.wait() is located.
}
semaphore.wait() // Wait for semaphore.signal() to fire, then continue to return
return sum // only after semaphore.signal() fires will this code run
}

Letter by letter animation with uitextfield when tap gesture executes is executing at same time

new to Stack Overflow and relatively new to swift. I'm trying to execute a letter by letter animation when the screen is tapped, but the letters appear scrambled when the screen is tapped multiple times.
I'm pretty sure that this is probably because the next line of text is animating at the same time the current line of text is animating, but I'm not sure as to how I would make sure that the current line is done animating before the next. What would be the proper way to do this?
#objc func handleTap(sender: UITapGestureRecognizer? = nil) {
if counter < textArray.count {
storyTextView.text = textArray[counter] //
storyTextView.animate(newText: storyTextView.text ?? textArray[counter], characterDelay: 0.1)
counter += 1
}
}
extension UITextView {
func animate(newText: String, characterDelay: TimeInterval) {
DispatchQueue.main.sync {
self.text = ""
for (index, character) in newText.characters.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
self.text?.append(character) // animation function is running at same time
print("characterDelay \(characterDelay) index \(index)")
}
}
}
}
It seems that when user taps the second time basically 2 animations start to happen at the same time. Since you use 2 different texts as source and append character by character to create a new string you probably get interleaved string from it.
You are not specifying what a desired result would actually be when user taps the second time. The easiest would be to simply end the current animation and fill the whole current string. The hardest would be to animate both at the same time which I would not even want to go to deep on thinking about at the moment.
I believe in your case it would be better using a timer than a dispatch. You can create something like:
let timer = Timer.scheduledTimer(withTimeInterval: characterDelay, repeats: true, block: {{ _ in
// Append the character here
}})
It would also be better to deplete your string instead of indexing by doing something like:
let newText = oldText + String(textToAppend.remove(at: textToAppend.startIndex))
So this will remove the first character from source string and at the same time append it to the destination string.
Now to put it all together I would do the following:
protocol StringAnimatorDelegate: class {
func stringAnimator(_ sender: StringAnimator, didUpdateStringTo string: String)
func stringAnimator(_ sender: StringAnimator, didFinishWithString string: String)
}
class StringAnimator {
var delegate: StringAnimatorDelegate?
private(set) var text: String = ""
private var timerWithString: (timer: Timer, stringToAppend: String)?
func animateText(_ stringToAppend: String, intervalDuration: TimeInterval) {
if let timerWithString = timerWithString {
self.timerWithString = nil
// We have a string already to append. Finish it
timerWithString.timer.invalidate() // Stop the previous timer
self.text = self.text + timerWithString.stringToAppend // Append whatever is left to append
self.delegate?.stringAnimator(self, didFinishWithString: self.text)
}
// Create a new timer
let timer = Timer.scheduledTimer(withTimeInterval: intervalDuration, repeats: true) { timer in
guard let string = self.timerWithString?.stringToAppend, string.count > 0 else {
// String is either nil or depleted. Finish timer.
self.timerWithString?.timer.invalidate()
self.timerWithString = nil
self.delegate?.stringAnimator(self, didFinishWithString: self.text)
return
}
var depletingString = string // Make a mutable copy of string to deplete so we may modify it
self.text = self.text + String(depletingString.remove(at: depletingString.startIndex)) // Modify both strings
self.timerWithString = (timer, depletingString) // Assign new values
self.delegate?.stringAnimator(self, didUpdateStringTo: self.text)
}
self.timerWithString = (timer, stringToAppend)
}
}
This is now a whole system for animating the string. You can create an instance like:
let animator = StringAnimator()
animator.delegate = self
Then for instance on tap you simply call animator.animateText(... as you do now. And you extend your class to support a delegate to animator and on both methods simply update your text field or whatever text container you are using. Alternatively you can simply put this code into your class and use it directly as you seem to have been doing till now.

code is skipping firebase snapshot

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.

How to return a bool to the outside function when you have nested functions in Swift?

So I have a function I have that has an inner function that connects to a Firebase database. the function returns a bool but I need the to return true or false based on whats inside the Firebase database. So basically I need to return true or false inside of the Firebase function the problem is it thinks that I am trying to return to the Firebase function when I am actually trying to return to the outside function a simple example is this
func someOtherFunc(name: String, lastname: String){
}
func someFunc(name: String, lastname: String) -> bool {
someOtherFunc(name: name, lastname: lastname) {
if name == "George" {
return true
} else {
return false // will not work because some other func does not return a value
}
}
}
here is the code that I need fixed which is a little more complicated than the above function because the inner Firebase function runs asynchronously(in the background) and so all the code inside the function needs to run synchronously to return the correct value
Here is my function
func takeAwayMoney(_ howMuch: String) -> Bool{
if let notMuch = Int(howMuch) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let money = value?["money"] as? String ?? ""
//convert money to int
if let conMoney = Int(money) {
var conMoreMoney = conMoney
if conMoreMoney < notMuch {
print(" sorry you don't have enough money")
return false
} else {
conMoreMoney -= notMuch
let values = ["money": String(conMoreMoney)]
//update the users money
self.datRef.child("User").child(userID!).updateChildValues(values)
return true //doesn't work because of example above
}
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
This code does not compile because there is no return values to the main function.
I know the really hard way to fix this would be to initialize values at the top of the function that would be the money of the user then check it after dispatching it for a couple of seconds then you could return the values, but I know there must be another way because this way would cause a lot of problems.
The root cause is that takeAwayMoney is synchronous, but it uses observeSingleEvent, which is asynchronous.
The "right" way to solve this is to make takeAwayMoney return Void, but take a completion function that will give you the bool asynchronously, like this:
func takeAwayMoney(_ howMuch: String, completion: #escaping (Bool)->()) -> Void {
/// When you want to "return" the bool, call the completion. e.g.:
// ...
if conMoreMoney < notMuch {
print(" sorry you don't have enough money")
completion(false)
return // if you want to stop processing
}
// ...
}
If you cannot do this, then takeMoney needs to wait for the completion to finish and you use semaphores to have them communicate with each other. You do not just wait a couple of seconds. See this for an example:
Semaphores to run asynchronous method synchronously

How to stop viewWillAppear from completing until a function has finished

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.

Resources