Reputation: 1317
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!
Upvotes: 1
Views: 602
Reputation: 4176
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) } } }
Upvotes: 4
Reputation: 3083
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.
Upvotes: 2