I am trying to call a method with a completion handler, but I can't seem to call it without this error. I am confused on what its asking for.
Here is the method I can calling:
func fillFromFile(completionBlock: #escaping ([Asset_Content]) -> ()) {
let url = "URLSTRING"
LoadJSONFile(from: url) { (result) in
// The code inside this block would be called when LoadJSONFile is completed. this could happen very quickly, or could take a long time
//.map is an easier way to transform/iterate over an array
var newContentArray = [Asset_Content]()
for json in result{
let category = json["BIGCATEGORY"] as? String
let diagnosis = json["DIAGNOSIS"] as? String
let perspective = json["PERSPECTIVE"] as? String
let name = json["NAME"] as? String
let title = json["Title"] as? String
let UnparsedTags = json["TAGS"] as? String
let filename = json["FILENAME"] as? String
let tagArray = UnparsedTags?.characters.split(separator: ",")
for tag in tagArray!{
if(!self.ListOfTags.contains(String(tag))){
self.ListOfTags.append(String(tag))
}
}
let asset = Asset_Content(category!, diagnosis!, perspective!, name!, title!, filename!)
// This is a return to the map closure. We are still in the LoadJSONFile completion block
newContentArray.append(asset)
}
print("return count ", newContentArray.count)
// This is the point at which the passed completion block is called.
completionBlock(newContentArray)
}
}
Related
I have a var declared and I can retrieve the value from Firebase Database but when I then print the var in ViewDidLoad, it is empty, I don't understand what's wrong. Thanks everyone
This is the answer I get when I print the var : this is the language
//
var language: String = ""
//
func getUserLanguage(completion:((String) -> Void)?) {
let ref = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
ref.child("users").child(uid).observe(.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let languageResult = value?["language"] as? String ?? ""
completion?(languageResult)
}
)}
//
getUserLanguage { (languageResult) in
self.language = languageResult
}
// I print the value in ViewDidload
print("this is the language\(self.language)")
Try to print languageResult in function - maybe you don't get it inside the function and the variable is not assigned
Make language variable public
I think you are missing the asynchronous nature of your code here. The getUserLanguage function will call the completion only when it gets callback from observe(.value, with: { (snapshot) method of firebase. It is asynchronous. You won't get the value of language right after you call getUserLanguage in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
getUserLanguage { (language) in
print(language) // --> prints the expected value
self.language = language
}
print(language) // --> prints ""
}
func getUserLanguage(completion: #escaping (String) -> Void) {
let ref = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
ref.child("users").child(uid).observe(.value, with: { (snapshot) in
let value = snapshot.value as? [String: Any]
let languageResult = value?["language"] as? String ?? ""
print("language: ", languageResult) // --> prints the expected value
completion(languageResult)
})
}
From the documentation I see that I can get some user data (which I'm already getting correctly), however, the way it's structured, it doesn't allow me to access the array outside of it, this is what I mean, I have a function:
func observe() {
let postsRef = Database.database().reference(withPath: "post")
struct test {
static var tempPosts = [Post]()
}
postsRef.observe(.value, with: { snapshot in
for child in snapshot.children {
if let childSnapshot = child as? DataSnapshot,
let data = childSnapshot.value as? [String:Any],
// let timestamp = data["timestamp"] as? Double,
let first_name = data["Author"] as? String,
let postTitle = data["title"] as? String,
let postDescription = data["description"] as? String,
let postUrl = data["postUrl"] as? String,
let postAddress = data["Address"] as? String,
let url = URL(string:postUrl)
{
// Convert timestamp to date
// let newDate = self.getDateFromTimeStamp(timestamp:timestamp)
// Store variables from DB into post
let post = Post(author: first_name, postTitle: postTitle, postDescription: postDescription, postUrl: url, postAddress: postAddress)
test.tempPosts.append(post)
}
}
self.posts = test.tempPosts
// HERE IT WORKS
print(test.tempPosts[0].postTitle , " 0")
self.tableView.reloadData()
})
// HERE IT DOESN'T WORK
print(test.tempPosts[0].postTitle , " 0")
}
and I'm trying to access the data where it says: // HERE IT DOESN'T WORK, how can I access that array outside of it? I need to call it later
The observe() method is asynchronous, so after you call postsRef.observe the code executed within that closure is run ONLY AFTER the application receives a response from Firebase, so there's a delay. All code after this call that's NOT stored within the closure will be executed immediately though.
So the .observe asynchronous function call is executed, and then the next line under // HERE IT DOESN'T WORK is executed immediately after. This is why this doesn't work because test.tempPosts doesn't contain any values until after the server response is received, and by that time, your print statement outside the closure has already run.
Check out this StackOverflow answer to get some more information about async vs sync.
Asynchronous vs synchronous execution, what does it really mean?
Also too, you may want to look into closures on Swift here.
If you want to access the value outside of the closure, you'll need to look into using a completion handler or a class property.
Edit:
Here's an example
func observe (finished: #escaping ([Post]) -> Void) {
// ALL YOUR CODE...
finished(test.tempPosts)
}
func getTempPosts () {
observe( (tempPosts) in
print(tempPosts)
}
}
I'm having some trouble with an array. I created an array called 'coins'
var coins = [Coin]()
then appended objects to it within a function
func getCoinData() {
AF.request("https://min-api.cryptocompare.com/data/top/mktcapfull?limit=10&tsym=USD", encoding: JSONEncoding.default).responseJSON { response in
if let json = response.result.value{
let responseDictionary = json as! [String : Any]
let data = responseDictionary["Data"] as! [Any]
for index in data {
let coin = index as! Dictionary<String, Any>
let coinInfo = coin["CoinInfo"] as! Dictionary<String, Any>
let displayInfo = coin["DISPLAY"] as! Dictionary<String, Any>
let usdDisplayInfo = displayInfo["USD"] as! Dictionary<String, Any>
let name = coinInfo["Name"]
let fullName = coinInfo["FullName"]
let imageUrl = coinInfo["ImageUrl"]
let price = usdDisplayInfo["PRICE"]
let marketCap = usdDisplayInfo["MKTCAP"]
let change24Hr = usdDisplayInfo["CHANGE24HOUR"]
let newCoin = Coin()
if let newCoinName = name, let newCoinFullName = fullName, let newCoinImageUrl = imageUrl, let newCoinPrice = price, let newCoinMarketCap = marketCap, let newCoinChange24hr = change24Hr {
let coinName = newCoinName
let coinFullName = newCoinFullName
let coinImageUrl = newCoinImageUrl
let coinPrice = newCoinPrice
let coinMarketCap = newCoinMarketCap
let coinChange24Hr = newCoinChange24hr
newCoin.name = "\(coinName)"
newCoin.fullName = "\(coinFullName)"
newCoin.imageURL = "\(coinImageUrl)"
newCoin.price = "\(coinPrice)"
newCoin.marketCap = "\(coinMarketCap)"
newCoin.change24Hr = "\(coinChange24Hr)"
self.coins.append(newCoin)
}
}
}
}
}
When i print 'self.coins.count' within the scope of the function i can see the count incrementing. Outside the function it's reading 0 items in the array.
Written for Swift 5
The problem is that you have a URL request which is Asynchronous. This means that the task is not waited for to complete.
In your problem, inside the function coins is printed after it has been assigned, after the URL request. However, when coins is printed outside the function, it is printed before it has been changed, as the URL request has not yet completed.
To solve this, you need to create a completion handler. A basic one is shown here:
// Our errors which could occur
enum SomeError: Error { case unknown }
// Function which is ASYNCHRONOUS
func someAsyncFunction(completion: #escaping (Result<Int, SomeError>) -> ()) {
// Temporary for this example
let success = true
let myNum = 3
// Return value if it is a success, otherwise return the error
if success {
completion(.success(myNum))
} else {
completion(.failure(.unknown))
}
}
// Call
someAsyncFunction { (result) in
print("Result: \(result)")
/* PRINT COINS HERE */
}
See a full guide on completion handlers using Result in Swift 5 at hackingwithswift.com.
How can I get the numberOfMarkers out of the reading method of Firebase in Swift?
if I use the function in the {} this will save and I will be can use it not in the {}?
docRef = Firestore.firestore().document("Markol/Markers")
docRef.getDocument{ (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int ?? 0
}
//Here i want to get the let numberOfMarkers
var markerArrayList = [GMSMarker]()
func makeAMarker(_ Latitude:Double , _ Longitude:Double , _ Title:String,Snippet:String) -> GMSMarker{
let GmMarker = GMSMarker()
GmMarker.position = CLLocationCoordinate2D(latitude: CLLocationDegrees(Latitude), longitude: CLLocationDegrees(Longitude))
GmMarker.title = Title
GmMarker.snippet = Snippet
GmMarker.icon = UIImage(named: "smallStoreIcon")
return GmMarker
}
getDocument is an asynchronous task, so numberOfMarkers is only accessible before the closing }.
Do whatever you want with numberOfMarkers inside the getDocument listener, you may need to refactor your existing code to accommodate this. For example:
docRef = Firestore.firestore().document("Markol/Markers")
docRef.getDocument{ (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {return}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int ?? 0
processMarkers(numberOfMarkers, myData)
}
If this approach isn't clear, try posting more of your code in your question so others can help you restructure.
No you can't. Variable/constant is always visible just inside scope where is declared, between curly braces {...}.
What you probably want to do is to get this value to return it or use somewhere else. Don't do it, since getting data from Firestore is asynchronus task, use completion handler instead and return value (or nil if you don’t have value) as completion's parameter when you have it
func call(completion: #escaping (Int?) -> Void) {
...
docRef.getDocument{ docSnapshot, error in
guard let docSnapshot = docSnapshot, docSnapshot.exists else {
completion(nil)
return
}
let myData = docSnapshot.data()
let numberOfMarkers = myData?["NumberofMarkers"] as? Int
completion(numberOfMarkers)
}
}
then when you need to call it
call { numberOfMarkers in // code inside this closure is called with parameter of type `Int?` when you receive data and call completion from inside `call`
if let number = numberOfMarkers {
... // do something with it
}
}
... here you can use it for next purpose
I have written the following function to search through my Firebase database and I have also looked into using debug statements and tested with breakpoints to see this function is pulling the correct data and it is. But when I return the array at the end, the array is empty. As far as I understand this is due to the asynchronous nature of firebase. The function is getting to the end before the data is being added to the array. How do I fix this so it can work as intended, I want to return an array of items which I can then use for other functions.
static func SearchPostsByTags(tags: [String]) -> [Post]{
var result = [Post]()
let dbref = FIRDatabase.database().reference().child("posts")
dbref.observeSingleEvent(of: .value, with: { snap in
let comps = snap.value as! [String : AnyObject]
for(_, value) in comps {
let rawTags = value["tags"] as? NSArray
let compTags = rawTags as? [String]
if compTags != nil {
for cTag in compTags! {
for tag in tags {
if (tag == cTag) {
let foundPost = Post()
foundPost.postID = value["postID"] as! String
foundPost.title = value["title"] as! String
result.append(foundPost)
}
}
}
}
}
})
return result
}
}
You are returning your array before the async call ends. You should fill your array inside the async call and call then another method, which provides the results.
static func SearchPostsByTags(tags: [String]) {
let dbref = FIRDatabase.database().reference().child("posts")
dbref.observeSingleEvent(of: .value, with: { snap in
let comps = snap.value as! [String : AnyObject]
var result = [Post]()
for(_, value) in comps {
let rawTags = value["tags"] as? NSArray
let compTags = rawTags as? [String]
if compTags != nil {
for cTag in compTags! {
for tag in tags {
if (tag == cTag) {
let foundPost = Post()
foundPost.postID = value["postID"] as! String
foundPost.title = value["title"] as! String
result.append(foundPost)
}
}
}
}
}
// Call some func to deliver the finished result array
// You can also work with completion handlers - if you want to try have a look at callbacks / completion handler section of apples documentation
provideTheFinishedArr(result)
})
}