Array returning a blank array when outside of the PFQuery. For some reason, the items are not being passed to the array when compiled.
class DriverViewController: UIViewController {
var placesArr : Array<Place> = []
override func viewDidLoad() {
super.viewDidLoad()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var query = PFQuery(className:"places")
query.whereKey("username", equalTo:"email#email.com")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
println("Successfully retrieved \(objects!.count) scores.")
if let objects = objects as? [PFObject] {
for object in objects {
let x = Place(aIdent: (object["Ident"] as! Int), aName: (object["name"] as! String), aAddress: (object["originAddress"] as! String), aCity: (object["originCity"] as! String), aCategoryName: (object["catName"] as! String), aLat: (object["aLat"] as! String), aLng: (object["aLng"] as! String))
self.placesArr.append(x)
println(placesArr) //****It works here and prints an array****
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
println(placesArr) //****But here it returns a blank array and this is where I need it to return an array****
This a very common misunderstanding relating to threading, the issue is what order events run:
// Runs 1st
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
// Runs 3rd
}
// Runs 2nd
println(placesArr)
The execution of the program doesn't halt when you call findObjectsInBackground, it finds objects: inBackground which means the heavy lifting of a network request is dispatched to a different queue so that the user can still interact with the screen. A simple way to do this would be to do:
var placesArray: [Place] = [] {
didSet {
// Do any execution that needs to wait for places array here.
}
}
You can also trigger subsequent actions within the parse response block, I just personally find executing behavior dependent on a property update being executed in didSet to be a nice way to control flow.
Logan's answer is right in the money.
See my answer in this thread: Storing values in completionHandlers - Swift
I wrote up a detailed description of how async completion handlers work, and in the comments there is a link to a working example project the illustrates it.
query.findObjectsInBackgroundWithBlock is a block operation that performs in the background - it's asynchronous.
The line println(placesArr) actually executes before the block is finished - that's why you see nil there.
Related
I have a problem and I could really use some help..
I have the method below and everything works fine, until line 907.. When it comes for the object3.saveInBackgroundWithBlock, it does nothing.. Not even errors! It never saves the object and it never goes inside the block..
Any idea why?
func addUserToThoseIFollow(sender: UIButton) {
//self.navigationItem.rightBarButtonItem?.enabled = false
sender.enabled = false
let userQuery = PFQuery(className: "_User")
let userQuery2 = PFQuery(className: "_User")
userQuery.getObjectInBackgroundWithId(PFUser.currentUser().objectId) { (object: PFObject!, error: NSError!) -> Void in
if error == nil {
// If I already follow some users, make
// an array with them, add the user I
// want to follow and save. Else,
// just save an array, with that one user.
if object["following"] != nil {
var thoseIFollow = object["following"] as! [String]
thoseIFollow.append(self.userID!)
object["following"] = thoseIFollow
}
else {
var myUsers = [String]()
myUsers.append(self.userID!)
object["following"] = myUsers
}
object.saveInBackgroundWithBlock({ (ok: Bool, error2: NSError!) -> Void in
if error2 == nil {
self.followButton.setTitle("Unfollow", forState: .Normal)
self.followButton.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.7)
sender.enabled = true
self.doIFollow = true
}
})
}
}
// Add me to his followers
userQuery2.getObjectInBackgroundWithId(self.userID) { (object3: PFObject!, error3: NSError!) -> Void in
if error3 == nil {
// If the user I just followed, has already followers, make
// an array with them and add the current user to
// them. Else, just save an array, with the current user.
if object3["followers"] != nil {
var hisFollowers = object3["followers"] as! [String]
hisFollowers.append(PFUser.currentUser().objectId)
object3["followers"] = hisFollowers
/* Line 907 */ object3.saveInBackgroundWithBlock({ (ok7: Bool, error7: NSError?) -> Void in // Line 907
if error7 == nil {
print("ok")
}
else {
print(error7)
}
})
}
else {
var hisFollowers = [String]()
hisFollowers.append(PFUser.currentUser().objectId)
object3["followers"] = hisFollowers
object3.saveInBackgroundWithBlock( { (ok5: Bool, error7: NSError!) -> Void in
print("otinanai")
if error7 != nil {
print(error7.localizedDescription)
}
})
}
}
}
}
Attempt #1
What PFUser.currentUser().objectId return ? If it returns nil so it doesn't work.
Attempt #2
Available Parse Types
So far we've used values with type NSString, NSNumber, and PFObject. Parse also supports NSDate, and NSNull.
You can nest NSDictionary and NSArray objects to store more structured data within a single PFObject.
Try to use var hisFollowers = [NSString]() instead of var hisFollowers = [String]()
self.userID
Where exactly is this coming from?
Did your check, whether it is an optional?
Comment out the first query and see if it works.
Each Parse object can only have one background thread running for it at a time. Say you save an object, then immediately in the next line (not inside of its call back), edit it and then save it again. The second save will not be called, since the first save is still running. You don't even get an error. You get zero notification whatsoever that this call didn't happen.
My guess is that you have objects being saved inside both the first query and the second query, and because of that, the second query's save is being skipped.
The solution would be to stick the second query inside of the first's callback.
I think that there's a PromiseKit library you can download that adds javascript functionality in iOS, making it more similar to how you'd chain these calls in cloud code, but I haven't used it.
Apologies that I couldn't think of a better way to title this.
Basically I have an app which connects to Parse. I have specified an action, which runs when a text field is changed (i.e when the user types a letter)
within this action I'm calling a PFQuery to Parse, which I then ask to findObjectsInBackgroundWithBlock.
The problem is that if a user were to type another letter before this query has finished running, then 2 queries are now running and the results of both end up populating the tableView.
So my question is simply, if the user were to type in another letter before the first findObjectsInBackgroundWithBlock has finished, how would I cancel the first and run a new one?
I have tried inserting PFQuery.cancel(query) at the start of the action, but the code gets confused as there isn't a query running yet when the action runs for the first time.
My code, incase it may help:
#IBAction func textFieldChanged (sender: AnyObject) {
let query = PFUser.Query()
query!.whereKey("Postcode", containsString: searchField.text)
query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.citiesArray.append(object["city"] as! String)
}
}
})
Many thanks for your patience!
You can try wrapping those requests in NSOperation and adding them to a dedicated (for such search requests) NSOperationQueue and calling cancelAllOperations() when a new character is typed.
In NSOperation's inner Parse block check for self.cancelled and return doing nothing if cancelled. Should work fine.
UPDATE:
class ViewController: UIViewController {
#IBOutlet weak var searchField: UITextField!
var citiesArray = [String]()
lazy var textRequestQueue: NSOperationQueue = {
var queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = NSQualityOfService.UserInteractive
return queue
}()
#IBAction func textFieldChanged(sender: AnyObject) {
textRequestQueue.cancelAllOperations()
let query = PFQuery()
query.whereKey("Postcode", containsString: searchField.text)
textRequestQueue.addOperation(TextRequestOperation(query: query, resultBlock: { (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.citiesArray.append(object["city"] as! String)
}
}
}))
}
}
class TextRequestOperation: NSOperation {
typealias ResultBlock = ((result: String)->())
var _resultBlock: PFArrayResultBlock
var _query: PFQuery
init(query: PFQuery, resultBlock: PFArrayResultBlock) {
self._resultBlock = resultBlock
self._query = query
}
override func main()
{
if self.cancelled { return }
_query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if self.cancelled { return }
self._resultBlock(objects, error)
}
}
}
NSOperation is one option. ReativeCocoa is another option that could help you solve this problem quite easily if you know how to use it.
However the easiest way (and hackiest) would prob be to keep some state of the search and use it to only apply the most recent searches results.
var mostRecentSearchQuery: String = ""
#IBAction func textFieldChanged (sender: AnyObject) {
var queryString: String?
if let textField: UITextField = sender as? UITextField {
queryString = textField.text
self.mostRecentSearchQuery = textField.text
}
let query = PFUser.Query()
query!.whereKey("Postcode", containsString: searchField.text)
query?.findObjectsInBackgroundWithBlock({[weak self] (objects, error) -> Void in
if let objects = objects where queryString == self?.mostRecentSearchQuery {
for object in objects {
self.citiesArray.append(object["city"] as! String)
}
}
})
This will only update your results if block used the most recent text typed.
ps. I am assuming the sender passed into the method is the textField which text has changed.
I am creating an app for test taking in Swift, and I'm using Parse to handle the backend. There are two main "object" types that I am working with in Parse: Test and Question. Each Test object contains an array of Question objects called "questions". I need to capture the questions array of the test object with the getObjectInBackgroundWithId method, as I have the test's objectId value, and save it to an array I have declared earlier in the method. When I assign the array to my questions array from the beginning of the method inside of the closure, I print it, and it appears to have been correctly copied, but when I print it outside of the closure, it hasn't been copied. Here is the method:
#IBAction func endTestPressed(sender: UIButton)
{
let lab = self.pinLabel.text!
var questions = [PFObject]()
let query = PFQuery(className:"Test")
query.getObjectInBackgroundWithId(lab.substringFromIndex(advance(lab.startIndex,5)))
{
(test: PFObject?, error: NSError?) -> Void in
if error == nil && test != nil
{
questions = test?["questions"] as! [PFObject]
print("Inside of closure: \(questions)")
}
else
{
print(error)
}
}
print("Outside of closure: \(questions)")
}
How can I save the array from Parse as an array declared in the method before the closure?
It is not that the array is empty in the outside the closure, what happens is that getObjectInBackgroundWithId happens in the background, the rest of your app still running, so you first print the outside println command, than when the results come back from the background thread it just run the completion block
#IBAction func endTestPressed(sender: UIButton)
{
let lab = self.pinLabel.text!
var questions = [PFObject]()
let query = PFQuery(className:"Test")
query.getObjectInBackgroundWithId(lab.substringFromIndex(advance(lab.startIndex,5)))
{
//Run when the getObjectInBackgroundWithId return with the results
(test: PFObject?, error: NSError?) -> Void in
if error == nil && test != nil
{
questions = test?["questions"] as! [PFObject]
print("Inside of closure: \(questions)") //this happen after the print outside the closure as the request happens in the backgroun
}
else
{
print(error)
}
}
//Application continue run while the getObjectInBackgroundWithId retrives data from Parse.com
print("Outside of closure: \(questions)") //This will happen first and the array is not populate yet
}
I am using Parse.com as my backend. I would like to put all functions/code related to accessing Parse.com in a single class that I can call from different ViewControllers.
Problem is that - since many of these functions from Parse.com are asynchronous, how does one return a value from these functions to update the UI?
For example, in the following function I am going and getting Earnings information of the current user. Since this function is using the asynchronous method findObjectsInBackgroundWithBlock from parse.com, I cannot really return anything from this function.
Is there a workaround to this problem? Currently, I am having to place this in function in the same ViewController class. (and hence having to repeat this same function in multiple viewControllers. Would like to have it in a single function only)
Only solution I see is to go to the synchronous method findObjects. Is there any other way?
func getcurrUserEarnings() {
/// Get details of currentUser from Earnings Class
///
/// :param - NSInterval
/// :returns - Int
func loadEarningsInfo() {
if (PFUser.currentUser() != nil) {
var query = PFQuery(className:"Earnings")
query.whereKey("user", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
//WANT TO UPDATE UI HERE WITH THE VALUES THAT WERE JUST RETURNED
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
}
You can use callback to pass in something.
For example:
func doSomething(callBack:(String)->())->(){
callBack("abc")
}
doSomething { (str:String) -> () in
println(str)
}
Also, do not forget to update UI on main thread
For example
func loadEarningsInfo(callBack:([PFObject])->()) {
if (PFUser.currentUser() != nil) {
var query = PFQuery(className:"Earnings")
query.whereKey("user", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
if let objects = objects as? [PFObject] {
callBack(objects)
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
}
Then when you use
loadEarningsInfo { (objects:[PFObject]) -> () in
//Update UI with objects
}
You can also handle error in callback as well,I just post a simple example
I want to be able to organize the data i am receiving from parse by creation date, How do i do that?
I looked at the parse docs online, but i could not find anything that said how to query data by creation date. I also saw an answer of stack over flow, but it was in objective-c. Please answer in swift.
Here is the code i am using now to receive my data...
var query = PFQuery(className:"Class Name")
//this is what i tried..
query.orderByDescending("createdAt")
//
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
self.NameArray.insert(object.valueForKey("Name")! as! String, atIndex: 0)
self.TextArray.insert(object.valueForKey("Text")! as! String, atIndex: 0)
}
}
} else {
// Log details of the failure
self.alert("Error: \(error!.localizedDescription)", Message: "Make sure you have a secure internet connection")
}
dispatch_async(dispatch_get_main_queue()) {
println("Finished importing")
self.collectionView.reloadData()
}
}