Getting values from callback function not working [duplicate] - ios

This question already has answers here:
Waiting for Asynchronous function call to complete
(2 answers)
Closed 5 years ago.
I have a callback function that returns an NSDictionary:
override func viewDidLoad()
{
super.viewDidLoad()
var nd: NSDictionary = [:]
parseJSON(callback: {dict in
nd = dict
//print(nd) This one prints the filled dictionary correctly
})
//print(nd) This one prints an empty dictionary
}
I want to store the values returned from the callback function in "nd", but the print statement outside the callback is still printing an empty NSDictionary.
What am I doing wrong here, and how can I fix it?
EDIT:
var nd: NSDictionary! = nil
override func viewDidLoad()
{
super.viewDidLoad()
loadData()
print(self.nd)
}
func loadData()
{
parseJSON(callback: {dict in
self.nd = dict
print(self.nd)
})
}
I changed it to this, so the function completes its loading, and then prints. However, the print statement in viewDidLoad() prints before the one in loadData(), and the print statement in viewDidLoad() still prints nil. Why does this happen, and what do I need to change specifically?

Looks like the parseJSON runs in the background and might only complete after your viewDidLoad method ends. As such, nd will stay empty immediately after the call.
My suggestion: add your dictionary processing logic inside the aforementioned callback (and then copy the results to a variable outside your viewDidLoadmethod).

Related

ViewDidLoad not performing actions in the right order - Firebase, Swift

When I try to run the following code on a Simulator (also on a Device, the result is the same) I have a problem: the function executes first the part 2 of the following code (the comments are there just to have a reference, the code itself is entirely in the viewDidLoad. Just to be clear) and then it executes the part 1 (the firebase part). I need the ref.child.... function to be performed before the other function because the "createAlbums" func needs the albums taken from my database (So a completion block would be perfect, but that function doesn't allow me to put any).
I thought about a pair of solutions:
A way to add a completion block (remember that the ref.child... func is like a cycle so it will print the entire "albums" array for every single value that it finds on the database.
Create a Int var and make the 2 part of the code run only if the value of the var is the same as the number of values of "Albums" in the database, so it will fill up the local "Albums" array.
Use two different functions: the the viewDidLoad and the viewDidAppear. As I know, the second one will be performed after the first one.
Which one is the best solution? Is there any better solution?
I also ask you to make suggests on how I can create my own solutions, because I can logically arrive to the point, but I'm not sure to be able to develop them in Xcode.
Just to summarize the entire thread: how can I make the 2 part of the following code run after the 1 part? And why does it run before, even if the order of writing is correct and the compiler runs from the line 1 to the last line?
Thank you for your help and sorry for my bad English, I hope that you can understand everything, unfortunately trying to explain a "complex" problem in English is not always easy.
But I think that the moderators will be great as always to make some corrections!
// 1
let ref = Database.database().reference()
var albums: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
ref.child("Albums").observe(.childAdded) { (snapshot) in
albums.append(snapshot.key)
print("\(albums)")
print("Test print")
}
// 2
createAlbums()
setupConstraints()
}
You'll need to move createAlbums() and setupConstraints() into where you are doing Test Print.
Although it looks like your code is all happening in one place, it's really not.
These lines, between the curly braces...
albums.append(snapshot.key)
print("\(albums)")
print("Test print")
... are happening asynchronously, when firebase calls you back because it has data.
So you need something more like this:
// 1
let ref = Database.database().reference()
var albums: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
var createdAlbums = false
ref.child("Albums").observe(.childAdded) { (snapshot) in
albums.append(snapshot.key)
print("\(albums)")
print("Test print")
if !createdAlbums {
// Only run this part once, when we
// get our first callback from firebase
self.createAlbums()
self.setupConstraints()
createdAlbums = true
}
}
// 2
}
OTHER WORKING SOLUTION
In that case, observe the .value event on the same reference/query. In your closure, loop over the children as shown here stackoverflow.com/a/27342233, and at the end of the loop call createAlbums().
From a Frank van Puffelen comment

How to wait for firebase to load the data into a variable before returning the variable then call the function [duplicate]

This question already has answers here:
Wait for Firebase to load before returning from a function
(2 answers)
How to return value from an asynchronous callback function? [duplicate]
(3 answers)
Closed 5 years ago.
This Question is not a duplicate.
So I have a simple function that will take a parameter of the index of what tableview cell was clicked so the function will go into the didSelectRowAt tableView function, and it will also return a String.
The Function is called getOpposingUserName and it connects to firebase to gather the information provided inside the database.
Now I know that the functionality of the function works it is sort of a logic error that I don't quite understand. From what I know is that whenever you try to get data from firebase it runs that code and takes a second to actually get the data. but as its doing that it keeps running the code below where you tried to get data from the database.
which means if I want to return a String then the string will always be nil or whatever I set its initial value to, because the database will take to long to load and the function will already have ran through the code to return something.(that's my understanding of it, I'm sure there is a way more technical way to explain it)
so basically what I am asking is how would I "load" the data into a variable first then check that firebase has indeed loaded something into the variable and then return the variable for whatever called the function
This question IS not a duplicate I have read the question and tried to make sense of it in my own implementation. I am not returning a function that code is confusing to me when I read it.
here is my code for the function
func getOpposingUsername(_ index: Int) -> String {
var opposingUser: String = ""
//the self.tieBetToUser just gets the randomized id from the bet in firebase don't need to worry about it
self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
guard let dict = snapshot.value as? [String: AnyHashable] else {
return
}
opposingUser = dict["OpposingUsername"] as! String
print(opposingUser) //this code actually never runs I found out
})
//sleep(4) this will not work and is actually bad code anyway different phones may be faster or slower
return opposingUser // will always return "" at the moment
}
here is the solution the internet has provided
typealias someting = (String?) -> Void
func getOpposingUsername(completionHandler: #escaping someting, _ index: Int) {
var opposingUser: String = ""
self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
guard let dict = snapshot.value as? [String: AnyHashable] else {
return
}
opposingUser = dict["OpposingUsername"] as! String
if opposingUser.isEmpty {
completionHandler(nil)
} else {
completionHandler(opposingUser)
}
})
}
How exactly would I call this function somewhere else? like
getOpposingUsername(somethingGoesHere, index.path)

Swift how to make a function execute after another function completed

I wrote two Swift functions (just a multiple choice quiz app)
func createQuestions() // this goes to Parse, and fetches the questions data that are going to question the users and store them into local arrays
func newQuestion() // this also fetches some other data (for example, some incorrect choices) from Parse and read local variables and finally set the labels to correctly display to the users
I want in ViewDidLoad, first execute createQuestion(), after it is fully completed then run newQuestion(). Otherwise the newQuestion() has some issues when reading from local variables that were supposed to be fetched. How am I going to manage that?
EDIT: I learned to use closure! One more follow up question. I am using a for loop to create questions. However, the problem is that the for loop does not execute orderly. Then my check for repeated function (vocabTestedIndices) fails and it would bring two identical questions. I want the for loop to execute one by one, so the questions created will not be overlapped.
code image
try
override func viewDidLoad() {
super.viewDidLoad()
self.createQuestions { () -> () in
self.newQuestion()
}
}
func createQuestions(handleComplete:(()->())){
// do something
handleComplete() // call it when finished stuff what you want
}
func newQuestion(){
// do other stuff
}
What about swift defer from this post?
func deferExample() {
defer {
print("Leaving scope, time to cleanup!")
}
print("Performing some operation...")
}
// Prints:
// Performing some operation...
// Leaving scope, time to cleanup!
Since you are new. I don't know if you do know closures or not so i have placed simple solution for you. (solution is similar to #Paulw11 commented on your question)
just call in viewDidLoad:
self.createQuestions()
The task you want to perform depends on the Parse response:
only after response arrives you want to call newQuestion function.
Here is the Parse Documentation for swift: https://www.parse.com/docs/ios/guide#objects-retrieving-objects
func createQuestions() {
var query = PFQuery(className:"GameScore")
query.whereKey("playerName", equalTo:"Sean Plott")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
self.newQuestion()
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
}
func newQuestion() {
//here is your code for new question function
}
Closure will help you to achieve this functionality.
Create your createQuestions function as below.
func createQuestions(completion:((Array<String>) -> ())){
//Create your local array for storing questions
var arrayOfQuestions:Array<String> = []
//Fetch questions from parse and allocate your local array.
arrayOfQuestions.append("Question1")
//Send back your questions array to completion of this closure method with the result of question array.
//You will get this questions array in your viewDidLoad method, from where you called this createQuestions closure method.
completion(arrayOfQuestions)
}
viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Calling createQuestions closure method.
self.createQuestions { (arrayOfQuestions) -> () in
//Pass your local questions array you received from createQuestions to newQuestion method.
self.newQuestion(arrayOfQuestions)
}
}
New Question Method
func newQuestion(arrayOfQuestions:Array<String>){
//You can check your questions array here and process on it according to your requirement.
print(arrayOfQuestions)
}

UIImage not show picture

Code
var promos = [PromoGroup]()
var adsImages = Array<UIImage>()
override func viewDidLoad() {
super.viewDidLoad()
setupData()
//print 1
println(self.promos.count)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setupData() {
getAllDataPromos { (PromoGroup, error) -> Void in
if error == nil {
self.promos = PromoGroup!
//print 2
println(self.promos.count)
for i in 0...self.promos.count-1 {
self.adsImages.append(UIImage(data: NSData(contentsOfURL: NSURL(string:"https://abs.com/content/bb2.jpg")!)!)!)
}
}
}
}
but adsImage can't show image, but when i try move adsImage to viewDidLoad and also it works, and result println
print 1 result
0
print 2 result
3
count data works if its on getAllDataPromos,
what should i do if i want to show picture in adsImage...
Thanks
(Adapted from this previous answer of mine: https://stackoverflow.com/a/28207368/2274694)
By invoking println(self.promos.count) immediately after the call to setupDataand thus getAllDataPromos contained within that method, you're attempting to print self.promos.count before it's been set in the asynchronous getAllDataPromos block.
An async block performs in the background, so as the code written immediately after your block executes on the main thread, the code within the async block will not yet be complete. That's why println(self.promos.count) prints correctly inside the async block -- because at that point, the code has definitely been executed; but you cannot print the updated value directly outside of the async block as you've done, because the async code hasn't finished executing yet.
Similarly, if you're trying to display an image in an adsImage UIImageView using a value from your adsImages array, you can't set that image until the asynchronous block is complete, so I'd recommend setting it within the async block.

How to find value of UIImage called in ViewDidLoad, pass to another function in Swift

I am using a simple function to call a random image when a view controller is loaded.
override func viewDidLoad() {
super.viewDidLoad()
var randomtest = ReturnRandom()
mainImage.image = UIImage(named: randomtest)
}
I want a button's action to be based on what image was displayed. For example
#IBAction func Button1(sender: AnyObject) {
switch randomtest {
case 1: //Do something here
case 2: //Do something different here
default:
}
However I can not figure out how to get the value of "randomtest" out of ViewDidLoad. I also can not seem to create the random number outside the ViewDidLoad then pass the variable in. Sorry I'm new to all this, iOS development is a long way away from php...
The reason I was having trouble declaring the result of my function as a instance variable is that my function is also an instance function. I can not call it, there has been no instance. So this
class ViewController : UIViewController {
var randomtest: Int = ReturnRandom();
Will return a "Missing argument for parameter #1 in call"
For more details please check out this very helpful thread on setting initial values. Since my function was so simple I just computed the property while setting the initial value, no need for an additional class level function.
dynamic var randomtest:String {
let imageNumber = arc4random_uniform(3)
var imageString = String(imageNumber)
return (imageString)}
Hope this helps someone.

Resources