Query class in Parse (swift) - ios

I have a strange issue. The following block of code is placed in my viewDidAppear section of the first View Controller, and when it runs, println(latArray), println(longArray), println(addressArray), all return no value. In the console it returns [] for all three arrays. HOWEVER, when I go to another view controller, and go back, it will populate with the data from Parse. Why is this? Why wont latArray, longArray, and addressArray populate when the app is loaded the first time with the viewWillAppear method?
var query = PFQuery(className: "NewLog")
// Add a where clause if there is a search criteria
query.whereKey("Type", containsString: newRecordCreated)
println(query)
query.findObjectsInBackgroundWithBlock({
(objects, error) -> Void in
if error == nil {
// Results were successfully found
if let objects = objects as? [PFObject] {
for object in objects {
//println(object["follower"])
latArray.append(object["Lat"] as! Double)
longArray.append(object["Long"] as! Double)
addressArray.append(object["Address"] as! String)
}
}
// self.tableView.reloadData()
} else {
println("error")
// The network was inaccessible and we have no cached data for
// this query.
}
})
println(latArray)
println("^^Latitude values")
println(longArray)
println("^^Longitude values")
println(addressArray)
println("^^Address values")
}

The query is being executed in the background. The code in the block (closure) is executed when the query is finished. Note: this might be and most likely is after viewDidAppear finishes. If you put your print statements after your for loop, you should see values. If you uncomment your reloadData method, the table should be updated with new information when the query finishes.

Related

GCD with nested Parse Queries

func getPosts(skip: Int){
var query = PFQuery(className: self.parseClassName!)
query.includeKey("posted_by")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil && objects != nil {
if let objects = objects as? [PFObject] {
var requestGroup = dispatch_group_create()
for post in objects
{
dispatch_group_enter(requestGroup)
let queryKommentar1 = PFQuery(className:"Comment")
queryKommentar1.whereKey("posted_to", equalTo: post)
queryKommentar1.limit = 3
queryKommentar1.includeKey("written_by")
queryKommentar1.findObjectsInBackgroundWithBlock() {
(commentObjects: [AnyObject]?, error: NSError?) -> Void in
//Creating UITableViewCells from data and store in array
dispatch_group_leave(requestGroup)
}
}
println("Successfully retrieved \(objects.count) posts.")
dispatch_group_notify(requestGroup, dispatch_get_main_queue()) {
println("All done")
}
}
}
}
}
So, I'm not sure if I misunderstood dispatch groups, but my intention is to make two Parse Queries, targeting different classes, and create TableViewCells from the data provided. This works fine, but since I don't want the data to load when the user is scrolling the table, I want to preload the data and create the cells, and store them in an Array. Since I would like to remove any Activity Indication, and reload the table, by the time the fetch is complete I though (after som Googleing) that dispatch groups might be a good solution for this. However, "All done" is never printed in the console.
When I made a dispatch group around the outer query (entering the group just before the query, and leaving as the last line in the block) that worked fine. What am I doing wrong? Is it impossible to use this when nesting asynchronous calls?
FYI, I removed a lot of code, like creating the cells and using the data from Parse, since I would like to spare you from reading that mess.
This dispatch group pattern is basically right.
I would suggest logging some message at dispatch_group_enter and at dispatch_group_leave and see if it's getting called as you think it should and that every enter is offset by a leave.
If the number of occurrences of dispatch_group_leave are less than the number of calls to dispatch_group_enter, the dispatch_group_notify block will not get called.
Perhaps you have some path in that inner findObjectsInBackgroundWithBlock closure that is preventing it from hitting the dispatch_group_leave call.

Array index out of range on refresh

I have a Posts class that I am querying in a UITableViewController using Parse as my backend.
In my viewDidAppear I call my loadData() function. And then I have a var refresher = UIRefreshControl() that is responsible for my refresh() function.
After a few time reloading the data I get a a fatal error: Array index out of range and the following line of code highlighted which is in my cellForRowAtIndexPath.
What was interesting is when I printed out what was in my
index path using println(drive), all the posts were there. But then there were instances where some posts appeared twice then the app would crash.
timelineData.removeAll(keepCapacity: false)
I thought that having this should clear everything so I am not sure why this is happening.
Here is my code for my refresh function.
func refresh()
{
println("refresh table from pull")
timelineData.removeAll(keepCapacity: false)
var findItemData:PFQuery = PFQuery(className:"Posts")
findItemData.addDescendingOrder("createdAt")
findItemData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]? , error:NSError?) -> Void in
if error == nil
{
self.timelineData = objects as! [PFObject]
self.newsFeedTableView.reloadData()
}
}
self.refresher.endRefreshing()
}
I tried using Parse's query.cachePolicy but that didn't matter because the crash kept happening. https://parse.com/docs/ios/guide#queries-querying-the-local-datastore
I also thought it was because I have Parse.enableLocalDatastore() but still no luck.
I do call my other function loadData in my viewDidAppear as mentioned earlier, not sure if this might be the problem, but I don't know how to check for data when there is an update. Still not sure if this is the source of the problem.
EDIT 1
I have attached my timelinedata count in several functions. Second image is when I print the count in my cellForRowIndexPath
Try to:
findItemData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]? , error:NSError?) -> Void in
if error == nil
{
timelineData.removeAll(keepCapacity: false)
self.timelineData = objects as! [PFObject]
self.newsFeedTableView.reloadData()
}
}
It could happen that you have inconsistent data before you actually populate your list. This way you will have your data if some kind of error occurs, so you are safe from that point as well.

Sequence in codes inside a function - Swift

I have a function which will be triggered when a like button is clicked, something like the facebook "like" buttons. I have the first part, and the second part as commented below. The problem is that, when the code runs, there is no sequence in the codes. For example I need the first part to run and then the second part, but sometimes the second part runs first. How can I add a sequence as priority to run?
#IBAction func likeBtn(sender: AnyObject) {
/* first part */
if likeTitle == "Like" {
var likeObj = PFObject(className: "likes")
likeObj["userName"] = PFUser.currentUser()!.username
likeObj["tweetObjectId"] = objectid.text
likeObj.save()
likeBtn.setTitle("Liked", forState: UIControlState.Normal)
}
/* second part */
var likeCount = PFQuery(className: "likes")
likeCount.whereKey("ObjectId", equalTo: objectid.text!)
var likedUsersCount = likeCount.countObjects()
var addLikeCountQuery = PFQuery(className: "comments")
addLikeCountQuery.whereKey("objectId", equalTo: objectid.text!)
addLikeCountQuery.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
for object in objects! {
var ob:PFObject = object as! PFObject
ob["numberOfLikes"] = likedUsersCount
ob.save()
}
}
}
The first part will only run if likeTitle == "Like", so if likeTitle != "Like" then your first part won't run, but the second part will always run.
Another problem could (keyword here is "could" :-)) be that in your first part, you call likeObj.save(). I don't know what your code looks like, but this could be asynchronous, meaning that it'll save whatever needs to be saved in a background thread to not block the main thread, and meanwhile your second part code will continue running on the main thread.
If that's the case, then maybe you should consider some sort of callback to be executed when the save operation is completed, and in that callback, you invoke your second part.

Swift Variables Scope Issue

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.

popToViewController Executing First

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.

Resources