How to use completion handler correctly [duplicate] - ios

This question already has answers here:
NSURLConnection sendAsynchronousRequest can't get variable out of closure
(1 answer)
How do I return the response from an asynchronous call?
(41 answers)
Closed 7 years ago.
I understand how completion handlers work, but im a bit confused on the syntax. Below is a function that, given a username, calls a parse query to find out the corresponding userId. The query ends after the function is returned (so it returns nil), which is why we need the completion handler. How do i implement it?
func getUserIdFromUsername(username: String) -> String {
var returnValue = String()
let query = PFQuery(className: "_User")
query.whereKey("username", equalTo: username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for object in objects {
returnValue = object.objectId!
}
}
}
return returnValue
}
NOTE: I know examples similar to this exist, but they are either not swift, or extremely lengthy. This is a short and concise version that contains Parse.

Here's how to implement it:
func getUserIdFromUsername(username: String, completionHandler: String -> Void) {
let query = PFQuery(className: "_User")
query.whereKey("username", equalTo: username)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let objects = objects {
for object in objects {
completionHandler(object.objectId!)
}
}
}
}
And here's how to use it:
getUserIdFromUsername("myUser") { id in
doSomethingWithId(id)
}

Related

Random row in Parse

I am creating an app that involves questions and facts and I need them to be selected at random. There is going to be a ton of different ones and I do not want to have to type in the ObjectId for each one of them. Is there a way to get the ObjectId of a random row so I don't have to write in the object Id for each question or fact that is in the class?
Three steps to accomplish what you want:
Know ahead of time or find out the number of questions you have to look through.
Create a random integer using arc4random() or some other method between 0 and that number.
Create a PFQuery on your question class with skip set to the random integer and limit set to 1.
Here is a function for retrieving your questions, only the first 1000, and saving them locally:
func saveAllObjectsLocally() {
let query = PFQuery(className: “Questions”)
query.limit = 1000
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects {
do {
try PFObject.pinAllInBackground(objects)
} catch let error as NSError? {
print("error \(error)")
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
}
Once they are saved locally, a random question can be selected:
func getRandomQuestion() -> PFObject? {
let query = PFQuery(className: “Questions”)
query.fromLocalDatastore()
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if let objects = objects {
let randomIndex = arc4random_uniform(UInt32(objects.count))
return objects[Int(randomIndex)]
}
}
return nil
}
This is going to be faster than accessing the network every time a random question is needed.
If you need to access the questions from the cloud you can add an index column to your Parse database and use that as a key to efficiently retrieve a random row.

Swift: How to cancel a PFQuery if function is run again before PFQuery is finished?

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.

findObjectInBackgroundWithBlock nested ios

I am developing app using ios, swift and parse.com as backend.
My problem is I need one query object result in second query object like below code. but when i use below code GUI become unresponsive for some time because of findObjects() method. I have used findObjectsInBackgroundWithBlock() instead but than tableview self.posts display only one record in tableview. I have 10 record in post table.
Can you guide me proper way how to resolve below issue.Actually I does not want to use findObjects() method.
var query = PFQuery(className:"Post")
var fquery = PFQuery(className: "Friends")
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
let user = PFUser.currentUser()
if let objects = objects as? [PFObject] {
for object in objects {
friendArray.removeAll(keepCapacity: false)
fquery.whereKey("whosefriend", equalTo: object["postusername"])
var fobjects = fquery.findObjects()
for fobject in fobjects {
friendArray.append(fobject["friendname"] as String)
}
if (contains(friendArray, user["fullname"] as String)) {
let post = Post(.......)
self.posts.append(post)
}
}
}
self.tableView.reloadData()
} else {
println("Error: \(error) \(error.userInfo!)")
}
}
One option is to make your "postusername" a pointer column in class Post that points to Friends class and then you would only need one query that would go something like:
var query = PFQuery(className:"Post")
query.includeKey("postusername") //this would include the object that it points to i.e. the Friends object you saved there
... then in your for loop ...
for object in objects! {
let friend = object["postusername"] // now friend is the Friends object
let friendName:String = friend["friendname"] as? String
friendArray.append(friendName)
}
Note: this requires you saving "postusername" as a PFObject of Class Friends. Parse iOS docs explain this well.
https://parse.com/docs/ios/guide
I have resolve the issue by using relational query.
var query = PFQuery(classWithName: "Post")
var fQuery = PFQuery(className:"Friends")
fQuery.whereKey("friendname", equalTo: cuser["fullname"])
query.whereKey("postusername", matchesKey:"whosefriend", inQuery:fQuery)

How to put asynchronous Parse.com functions in a separate Class in Swift?

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

Retrieve object from parse.com and wait with return until data is retrieved

How can I wait until data is retrieved from parse.com?
This is the function I have that returns an empty string since the response from parse.com is too slow. If I put a breakpoint inside the success area it will break "long" after the data is needed. I guess there is a way to get the data synchronous so it will wait?
func getObjectId(localPersonId:NSString) -> NSString{
var currentObjectId:NSString = ""
var query = PFQuery(className:"myClass")
query.whereKey("personId", equalTo:localPersonId)
query.whereKey("groupId", equalTo:self.currentGroupId)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// should not use a for loop since this should
// only return one row
for object in objects {
currentObjectId = object["objectId"] as NSString
}
} else {
// Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
}
return currentObjectId
}
In this case the getObjectId function will return an empty string. Anyone?
I realize this is 3 months old but although the Parse docs are incredibly good/useful, there isn't a whole lot out there answering IOS Parse related questions.
This should work. It uses a completion handler, which is a simple way of dealing with this issue.
(for more on completion handlers in asynch context: https://thatthinginswift.com/completion-handlers/ )
func getObjectId(localPersonId:NSString, completionHandler: (currentObjectId: [String]) -> ()){
var currentObjectId:NSString = ""
var query = PFQuery(className:"myClass")
query.whereKey("personId", equalTo:localPersonId)
//query.whereKey("groupId", equalTo:self.currentGroupId)
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil {
// should not use a for loop since this should
// only return one row
for object in objects {
completionHandler(currentObjectId: currentObjectId)
}
} else {
// Log details of the failure
NSLog("Error: %# %#", error!, error!.userInfo!)
}
}
}

Resources