dan martin
dan martin

Reputation: 1317

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!

Upvotes: 1

Views: 602

Answers (2)

rshev
rshev

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

villy393
villy393

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

Related Questions