I have a problem that's been irritating me for a couple of days now. I'm trying to work with a WKWebView in my Swift project. Below is my code:
var items: [String] = []
for i in 0...8 {
web_view.evaluateJavaScript("document.getElementsByClassName('channels-content-item yt-shelf-grid-item')[\(i)].innerHTML") { (result, error) in
//print(result ?? "nil")
//print(error ?? "nil")
let string_result: String = String(describing: result!)
items.append(string_result)
num += 1
//print(string_result)
}
}
print(items)
My problem is the fact that the program won't wait for the evaluateJavaScript functions to finish before going to the print(items), even though the items array gets populated via these functions. Any way I can get this to work?
Any help would be greatly appreciated, thanks in advance
What is inside of your completion handler I think runs on a different thread, meaning it is running AFTER your print happens. Since injecting javascript takes longer, your print is most likely always going to happen first.
Related
Somewhat confused by this error, very new to Firebase but I think this is more a swift programming issue on my part. A lot of the swift coding I'm trying to implement with firebase is new to me.
"Contextual closure type '(Result<StorageListResult, any Error>) -> Void' expects 1 argument, but 2 were used in closure body"
Starting with:
textRef.listAll { (result, error) in
for item in result!.items {
If I use 'item' within this closure alone such as:
let downloadTask = item.write(toFile: localURL) { url, error in
if let error = error {
print ("UNABLE TO DOWNLOAD FILES")
} else {
print("NEW FILE DOWNLOADED")
print(item)
}
}
Works perfectly.
But if I try and use item a 2nd time within the closure, such as:
let serverTimestamp = dateFormatter.string(from: itemTemp.getMetadata.updated())
I get the above error.
The aim of my code is to gain a list all items on my Firebase storage, then against each storage item firstly check its metadata updated date before deciding whether to download or not. However within the closure I can't seem to use item more than once. I either check its metadata or download...not both.
I've tried looking at closures, but struggling to see how I could potential expand the closure to incorporate what I want.
Any advice apprecaited
I have a few queries running to load variables with data from a Parse server into variables that are declared right below class. When I print the variable to the console within the query function, it prints correctly, but when I call it outside of the function or print it, it is empty. Any idea where I'm going wrong?
Variable declaration:
class AddTaskViewController: UIViewController, UITextFieldDelegate, UIPickerViewDataSource, UIPickerViewDelegate {
var varCost = ""
Query loading variable:
let varCostQuery = PFQuery(className: "Class1")
varCostQuery.findObjectsInBackground(block: { (objects, error) in
if error != nil {
print(error!)
} else {
varCostQuery.whereKey("Header", equalTo: self.HeaderSelection.text)
varCostQuery.findObjectsInBackground(block: { (objects, error) in
for object in objects! {
self.varCost = object["pricePerUnit"] as! String
print(self.varCost) //Option1
}
})
}
})
print(varCost) //Option2
When I print Option1, I get the data exactly like I'm looking for, but when I print Option2 or try to do anything with the varCost variable at this level, I get "" like the variable has never been updated.
This occurs because the code being passed to the findObjectsInBackground method is code that is run only once all the objects have been found.
Because this code will only be called when the objects have been found, this may take a bit of time, so this code is send to a background queue to wait for the objects to be found.
We don't want the rest of our code to pause and wait for this though! That would slow our program down a lot. So the program continues past this block until it completes. That's why Option 2 is empty, because findObjectsInBackground hasn't had time to get the objects yet so the code has jumped straight to where Option 2 is.
When the objects have finally been found, the block of code is called and Option 1 is printed.
This means that you can only be sure self.varCost will have the right value from within this block (or closure, as it is called in Swift).
I have a function that has a function within it in terms of a call to a firebase database.
The internal function sets the value of a variable from the wrapper function, but my output does not register this. When debugging it looks like this happens in reverse order. I'm new to swift and firebase so i'm just trying to get my head round this.
This is my function and output.
func checkIfUsernameExists(inUsername: String) -> String{
var aString = "false"
let ref = FIRDatabase.database().reference()
ref.child("-KohvyrIikykRsOP0XCx").observeSingleEvent(of: .value , with: {(snapshot) in
if snapshot.hasChild(self.username.text!){
print ("*** username already exists")
aString = "true"
}
})
print ("*** value of aString is: ", aString)
return aString
}
output is:
*** value of aString is: false
*** username already exists
Edit:
I phrased my question poorly i think.
What i meant to ask was how can i get the call back from firebase before processing the information its collected. I've bounced round SO and lots of blogs all pointing to Async, GCD and Completion handlers. None of which seemed to work or were easy enough for noob to get their head round.
Needless to say i've found my answer here.
Firebase Swift 3 Completion handler Bool
This is what i used:
func checkIfUsernameExists(userid: String, completionHandler: #escaping ((_ exist : Bool) -> Void)) {
let ref = FIRDatabase.database().reference()
ref.child("-KohvyrIikykRsOP0XCx").observeSingleEvent(of: .value , with: {(snapshot) in
if snapshot.hasChild(self.username.text!){
self.usernameCheck = "true"
completionHandler(true)
}
else {
self.usernameCheck = "false"
completionHandler(true)
}
})
}
It seems like that you are trying to get a value from firebase database. As you may know. data in your firebase database is stored remotely. This means that you need a relatively large amount of time to et your data, when compared with getting data from the disk.
Since it takes so long, the designers of the firebase API thought "Instead of making the UI wait and become unresponsive, we should fetch the data asynchronously. This way, the UI won't freeze."
In your code, this block of code:
if snapshot.hasChild(self.username.text!){
print ("*** username already exists")
aString = "true"
}
will be executed after it has fetched the data, which is some time later. Since the operation is done asynchronously, the rest of the function will continue to be executed, namely this part:
print ("*** value of aString is: ", aString)
return aString
And at this stage the closure hasn't been finished executing yet, aString is false.
This might sound counter intuitive, but think about it this way, you are calling a method here - observeSingleEvent. The code in the closure is meant to be a parameter. You are just telling the method to "Run this when you're done fetching the data". The data is fetched asynchronously, so the code after that executes first.
I have encountered numerous situations where a coder have used the guard keyword. And then later, in a seemingly almost identical situation the same coder in the same code does not use the guard keyword. I am aware that this may be a stupid question, so please don't bash it. When should I use the guard keyword and where shouldn't I?
Here is an example (there are many more). This is part of a script that is requesting data form an API.
//Here I am using guard
guard let json = json else {
//Now I am not using guard
if let error = error {
completion(.Failure(error))
} else {
//Error handling
}
return
}
Why not use the:
if let var1 = var1 {
//Keep on going
} else {
//Don't crash
}
syntax all the time instead of the guard syntax? At first glance it even seems to have more functionality, but I am certain that does not have to be the case.
One great benefit of the guard statement is that you know that if the condition is not satisfied then the execution flow gets stopped.
This is important for several reasons
Unwrapping
You can define unwrapped values which don't need a new scope { ... } to be available
func next(num:Int?) -> Int? {
guard let num = num else { return nil }
return num + 1
}
Readability
When you read the code you know that if the guard condition is not satisfied then the following lines won't be executed.
Semantics
You know a guard statement is there to check conditions required for the following block of code.
But I can replace every guard with an if
Sure. We could also replace every while and for with a goto in some languages. And we could always replace recursion with iteration (and viceversa).
But this doesn't necessarily means it is always a good idea.
Despite we can implement some behaviours with more then one programming "tool", we should still use the one that better fits that specific scenario.
func forwardGeocoding(address: String) {
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print(error)
return
}
if placemarks?.count > 0 {
let placemark = placemarks?[0]
let location = placemark?.location
let coordinate = location?.coordinate
print("\nlat: \(coordinate!.latitude), long: \(coordinate!.longitude)")
if placemark?.areasOfInterest?.count > 0 {
let areaOfInterest = placemark!.areasOfInterest![0]
print(areaOfInterest)
} else {
print("No area of interest found.")
}
}
})
var INITIAL_DESTINATION = forwardGeocoding(initialDestination)
var DESIRED_DESTINATION = forwardGeocoding(desiredDestination)
var location = CLLocationCoordinate2DMake(<#T##CLLocationDegrees#>, <#T##CLLocationDegrees#>)
Hello, I am trying to make a mapping app, and am having trouble with this part. What I want to do is be able to separate the INITIAL_DESTINATION latitude and longitudes. I have to do this to create a CLLocationCoordinate2DMake. What I have been trying to do is just use INITIAL_DESTINATION.latitude and INITIAL_DESTINATION.longitude, but I am continuingly facing the same error which is "Value of tuple type "()" has no member "latitude". This is also strange because it does not give that error for INITIAL_DESTINATION.longitude.
Any help or suggestions are greatly appreciated, and thank you for reading and taking the time to respond.
Your function returns nothing, and does nothing with the value returned in the asynchronous completion handler. You need to take the asynchronous result and use it in some fashion.
Try this: Put prints at the end of the function, and inside the completion handler, then run the code. What you'll see is that the function is done before the completion handler runs, because the code inside the block does not run until the remote web site returns an answer across the network. At that time Alamofire hands the result to your code in the completion block.
You'll also need to be aware that there are multiple queues in iOS, and UI changes can only be done on the main queue. The completion block does not run on the main queue, however, so likely to use the information returned from the network you'll need to use the dispatch_async function to call a function in your program and have it execute on the main queue.