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)
}
Related
I am a beginner and never worked close with CoreData. I have a JSON response, which results need to be shown in a Table View. I want implement a CoreData to my project.
JSON parsing (in Separate Swift file)
func parseJSON(with currencyData: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CurrencyData.self, from: currencyData)
for valute in decodedData.Valute.values {
if valute.CharCode != "XDR" {
let currency = Currency(context: self.context)
currency.shortName = valute.CharCode
currency.currentValue = valute.Value
}
}
do {
try context.save()
} catch {
print("Error saving context, \(error)")
}
} catch {
self.delegate?.didFailWithError(self, error: error)
return
}
}
And in my VC I want to load it in my tableView which takes data from currencyArray:
func loadCurrency() {
let request: NSFetchRequest<Currency> = Currency.fetchRequest()
do {
currencyArray = try context.fetch(request)
} catch {
print(error)
}
tableView.reloadData()
}
I start the parsing and load currency data in my VC:
override func viewDidLoad() {
super.viewDidLoad()
currencyNetworking.performRequest()
loadCurrency()
}
But as a result when app first launch my tableView is empty. Second launch - I have doubled data. Maybe this is because loadCurrency() starts before performRequest() able to receive data from JSON and save it to context.
I tried also to not save context in parseJSON() but first through delegate method send it to VC and perform save to context and load from context from here. Then tableView loads from the first launch. But then every time app starts in my CoreData database I see an increase of the same data (33, 66, 99 lines).
My goal is to save parsed data to CoreData once (either where I parseJSON or in VC) and change only currentValue attribute when user want to update it.
How to make it correct?
You need to first choose which is your source of truth where you are showing data from. In your case it seems to be your local database which is filled from some external source.
If you wish to use your local database with data provided from remote server then you need to have some information to keep track of "same" entries. This is most usually achieved by using an id, an identifier which is unique between entries and persistent over changes in entry. Any other property may be used that corresponds to those rules. For instance CharCode may be sufficient in your case if there will always be only one of them.
Next to that you may need a deleted flag which means that you also get deleted items from server and when you find entry with deleted flag set to true you need to delete this object.
Now your pseudo code when getting items from server you should do:
func processEntries(_ remoteEntries: [CurrencyEntry]) {
remoteEntries.forEach { remoteEntry in
if remoteEntry.isDeleted {
if let existingLocalEntry = database.currencyWithID(remoteEntry.id) {
existingLocalEntry.deleteFromDatabase()
}
} else {
if let existingLocalEntry = database.currencyWithID(remoteEntry.id) {
database.updateCurrency(existingLocalEntry, withRemoteEntry: remoteEntry)
} else {
database.createCurrency(fromRemoteEntry: remoteEntry)
}
}
}
}
And this is just for synchronization approach.
Now going to your view controller. When it appears you call to reload data from server and at the same time display whatever is in your database. This is all fine but you are missing a re-display once new data is available.
What you optimally need is another hook where your view controller will be notified when database has changes. So best thing to do is add event when your database changes which is whenever you save your database. You probably looking at something like this:
extension Database {
func save() {
guard context.hasChanges else { return }
try? context.save()
self.notifyListenersOfChangesInDatabase()
}
and this method would be called within your parseJSON and everywhere else that you save your database. Then you would add your view controller as a listener on your database as
override func viewDidLoad() {
super.viewDidLoad()
database.addListener(self)
currencyNetworking.performRequest()
loadCurrency()
}
How exactly will listener and database be connected is up to you. I would suggest using delegate approach but it would mean a bit more work. So another simpler approach is using NotificationCenter which should have a lot of examples online, including on StackOverflow. The result will be
Instead of database.addListener(self) you have NotificationCenter.default.addObserver...
Instead of notifyListenersOfChangesInDatabase you use NotificationCenter.default.post...
In any of the cases you get a method in your view controller which is triggered whenever your database is changed. And in that method you call loadCurrency. This is pretty amazing because now you don't care who updated the data in your database, why and when. Whenever the change occurs you reload your data and user sees changes. For instance you could have a timer that pools for new data every few minutes and everything would just work without you needing to change anything.
The other approach you can do is simply add a closure to your performRequest method which triggers as soon as your request is done. This way your code should be like
override func viewDidLoad() {
super.viewDidLoad()
loadCurrency()
currencyNetworking.performRequest {
self.loadCurrency()
}
}
note that loadCurrency is called twice. It means that we first want to show whatever is stored in local database so that user sees his data more or less instantly. At the same time we send out a request to remote server which may take a while. And once server response finished processing a reload is done again and user can view updated data.
Your method performRequest could then look like something as the following:
func performRequest(_ completion: #escaping () -> Void) {
getDataFromRemoteServer { rawData in
parseJSON(with: rawData)
completion()
}
}
I read some questions about that. But I still have issues with asynchronous functions.
For example: I have a viewController1 where a button perform a segue to a viewController2. In the viewController2 class, I initialize some values in another class file named exampleClass. These values are retrieved from Firebase database or location values. These values need a little moment to be retrieved. I return thes values from the exampleClass into my viewController2. I print these values in the viewController2 viewDidLoad().
My issue: The device doesn't wait that the values are retrieved and execute following functions. Result: When I touch the button, printed values are nil values. It can also make the app crash if I don't secure the code.
What I've found so far: I learned that I only have to call a func at the end of a Firebase snapshot (for example) like this:
userRef.observeSingleEvent(of: .value, with: { (snapshot) -> Void in
self.name = snapshot.value as! String!
print(self.name)
self.forceEnd()
}) { (error) in
print(error.localizedDescription)
}
I named this function forceEnd to be clear. This is not working for me. I also tried to create handlers but no positive results.
My question: How can I force the device to wait for the values to be retrieved before performing the following question?
How can I force the device to wait for the values to be retrieved before performing the following question?
You don't want to force the device to wait, only need to perform some operations once these values are retrieved from Firebase database.
Performing an operation asynchronously can be done in multiple ways like blocks, protocols, notifications, etc.
Generally, blocks are the more elegant approach.
Some sample code can be like:
func myFirebaseNetworkDataRequest(finished: () -> Void) { // the function thats going to take a little moment
...
print("Doing something!") // firebase network request
finished()
}
// usage of above function can be as-
override func viewDidLoad() {
myFirebaseNetworkDataRequest {
// perform further operations here after data is fetched
print("Finally! It took a lot of moments to end but now I can do something else.")
}
}
I declare arr1 and arr2 as arrays globally in my viewcontroller.
In the following function, I add to them in a parse query. Xcode gets buggy when I don't include "self" before arr1 inside the query, so I included it. Outside the query, in the space where I have marked below, I try to access arr1 and arr2. But regardless of whether I try self.arr1 or arr1, the array turns out to be empty at that point. I think this is some sort of scope issue with swift, and I've had quite a bit of trouble with it so any help would be much appreciated.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "vF") {
var destVC = segue.destinationViewController as vF
destVC.namePassed = fV
var query = PFQuery(className:"class")
query.whereKey("createdBy", equalTo:fV)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// The find succeeded.
NSLog("Successfully retrieved \(objects.count) records.")
// Do something with the found objects
for object in objects {
self.arr1.append(object["field1"]! as Int)
self.arr2.append(object["field2"]! as String)
}
} else {
// Log details of the failure
}
}
// I want to access arr1 and arr2 right here, but when I do they come up empty
// I have tried accessing with self.arr1 and arr1 but neither works
}
}
The findObjectsInBackgroundWithBlock is async so it will happen in the background thread while your program still running in the main thread, by the point your background thread comeback with the data you need you program is why passed the point you indicate.
One of the best solutions for your problem is to add observers for the arr1 and arr2 so you will be notified when it happens. Add observers to array can be a little bit trick and I want to keep it simple for you, so I would recommend you to create a boolean variable that tells you when the value finish change. To do so you will need to create the variable like this
var arrayDidChange:Bool = false {
didSet{
if arrayDidChange{
callFuncDataReadArrayHere()
}
}
}
Now everytime you change the values (add or edit) in the array you set arrayDidChange to true and in the end of callFuncDataReadArrayHere after do all you need to do you set arrayDidChange to false.
This way you will always be sure you will access the values in the array after it be populate.
I hope that helped you!
You should connect the segue directly from the controller instead of the cell. Implement didSelectRowAtIndexPath, and put the query there, and call performSegueWithIdentifier:sender: inside the completion block of the asynchronous method. Now, when you implement prepareForSegue, the arrays will be available to pass to the destination view controller.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var query = PFQuery(className:"class")
query.whereKey("createdBy", equalTo:fV)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// The find succeeded.
NSLog("Successfully retrieved \(objects.count) records.")
// Do something with the found objects
for object in objects {
self.arr1.append(object["field1"]! as Int)
self.arr2.append(object["field2"]! as String)
}
self.performSegueWithIdentifier("vF", sender: self)
} else {
// Log details of the failure
}
}
}
There will be some delay before the segue actually proceeds with this approach, because it won't happen until findObjectsInBackgroundWithBlock returns its results. If that's not acceptable, then you could still pass the arrays in prepareForSegue (in the same place I show after the "for object in objects" clause), but then the arrays will not be immediately available in the destination view controller, and you will have to deal with that.
Ive spent countless hours trying to figure out why I can't seem to take the object id of an object that has been saved to parse and use it in other sections of my code. Yes I have searched and found topics about this on stack overflow and other websites and I have read the parse.com documentation but none of the answers will allow me to fix this issue. I am pretty new to coding and I am only familiar with swift at the moment. If you can help me figure this out I would more than appreciate your help.
I declare objectID as a string variable at the top of the code. Later when a send button is tapped I save the data to parse successfully and attempt to capture the datas objectId by passing the objectID variable to the saveToParse function as an inout. The project does not show any errors. The code builds and runs. NSLog(id) shows me the proper objectId that I want in the console. However it does not show anything when NSLog(self.objectID) is called outside of the function.
Like I said, I'm trying to use this objectId outside the function in other parts of the code, but it just doesn't want to save the objectId to my variable. I've tried many other methods and this is the closest I have got to making it work.
Im new at this so hope I've explained my situation clearly, if you need any more information to solve this don't hesitate to ask. Thanks for reading :)
var objectID:String = String() // declared at the top (underneath Class)
#IBAction func sendQuestionTapped(sender: UIButton) {
// Send question and answers to Parse
saveToParse(&objectID)
// Save Question object ID
NSLog(self.objectID) // Test if objectID is still Question.objectId
// Move to the results view controller with information
performSegueWithIdentifier("toResultsSegue", sender: self)
}
func saveToParse(inout id:String) {
var Question = PFObject(className:"Question")
Question["question"] = questionToPass
Question["answers"] = arrayToPass
Question.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
// The object has been saved.
dispatch_async(dispatch_get_main_queue()) {
id = Question.objectId!
NSLog(id)
}
}
else {
// There was a problem, check error.description
if error != nil {
NSLog(error!.localizedDescription)
}
}
}
}
When the post button is pressed, the function below executes. In the function, all the objects that are retrieved using the Parse backend are appended to the groupConversation array, which is a global array. However, when I reference the array in the UITableViewController that is popped to towards the end of the function and use println() to print the content of the array, the array is empty. However, when I use println() in the UIViewController that contains this function the array is shown to contain one object. In the console, the println() of the UITableViewController that is popped to once the button is pressed, is executed before the println() of the UIViewController that contains the function below. How can I make the functon below execute completely before popping to the UITableViewController.
#IBAction func postButtonPressed(sender: AnyObject) {
//Adds Object To Key
var name=PFObject(className:currentScreen)
name["userPost"] = textView.text
name.saveInBackgroundWithBlock {
(success: Bool!, error: NSError!) -> Void in
if success == true {
self.textView.text=""
} else {
println("TypeMessageViewController Error")
}
}
//Gets all objects of the key
var messageDisplay = PFQuery(className:currentScreen)
messageDisplay.selectKeys(["userPost"])
messageDisplay.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil{
for object in objects {
var textObject = object["userPost"] as String
groupConversation.append(textObject)
}
} else {
// Log details of the failure
}
println("Type message \(groupConversation)")
}
navigationController!.popToViewController(navigationController!.viewControllers[1] as UIViewController, animated: true)
}
The problem is here messageDisplay.findObjectsInBackgroundWithBlock. As you are doing this in background thread, it will be separated from main thread. And your main thread will execute as it should be.
So it before finishing the task you main thread popping the view.
messageDisplay.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil{
for object in objects {
var textObject = object["userPost"] as String
groupConversation.append(textObject)
}
} else {
// Log details of the failure
}
println("Type message \(groupConversation)")
dispatch_async(dispatch_get_main_queue()) {
self.navigationController!.popToViewController(navigationController!.viewControllers[1] as UIViewController, animated: true)
return
}
}
Pushing and popping in background thread may cause problem. So get the main thread after executing the task in background and then pop in main thread.
In swift single statement closures automatically return the statement return value. In your specific case, it's attempting to return an instance of [AnyObject]?, which is the return value of popToViewControllerAnimated. The closure expected by dispatch_afteris Void -> Void instead. Since the closure return type doesn't match, the compiler complains about that.
Hope this helps.. ;)
You are running into a very common issue with asynchronous code. Both your ...InBackgroundWithBlock {} methods run something in the background (async).
The best example I have found to explain it is this:
When you start an async code block, it is like putting eggs on to boil. You also get to include something that should be done when they finish boiling (the block). This might be something like remove the shell and slice the eggs.
If your next bit of code is "butter bread, put eggs on bread" you might get unexpected results. You don't know if the eggs have finished boiling yet, or if the extra tasks (removing shell, slicing) has finished yet.
You have to think in an async way: do this, then when it is finished do this, etc.
In terms of your code, the call to popToViewController() should probably go inside the async block.