As the title states, for some reason, the following (simplified) code is not working:
extension InputView: {
func updateTable(text: String) {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: nil)
//NSObject.cancelPreviousPerformRequests(withTarget: self)
self.perform(#selector(loadPlaces(text:)), with: text, afterDelay: 0.5)
prevSearch = inputField.text!;
}
//Private wrapper function
@objc private func loadPlaces(text: String) {
print("loading results for: \(text)")
// locator?.searchTextHasChanged(text: text)
}
}
I call updateTable
every time a user edits a UITextField
, which calls localPlaces
which calls a function that queries google's online places API (commented out). Unfortunately, the print line in loadPlaces is called after every single call to updateTable. From my visual inspection, it seems there is in fact a delay to the print statements, however, the old calls do not cancel. I've tried looking on a lot of StackOverflow threads but I couldn't find anything updated for Swift 3. Am I calling something incorrectly?
PS. If I instead use the commented out, single-argument, cancelPreviousPerformRequests
. It works for some reason.
Edit: I have been able to replicate this error in a separate project. So I'm 100% sure that the above code is wrong. If you would like to replicate this error, open up a new iOS project and paste the following code into the default ViewController
:
class InputView: UIView {
func updateTable(text: String) {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: nil)
self.perform(#selector(loadPlaces(text:)), with: text, afterDelay: 0.5)
}
//Private wrapper function
@objc private func loadPlaces(text: String) {
print("loading results for: \(text)")
// locator?.searchTextHasChanged(text: text)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let input = InputView()
for i in 0..<200 {
input.updateTable(text: "Call \(i)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The explanation in Duncan C's answer is not appropriate for this case.
In the reference of cancelPreviousPerformRequests(withTarget:selector:object:)
:
Discussion
All perform requests are canceled that have the same target as
aTarget
, argument asanArgument
, and selector asaSelector
.
So, when you have a line like:
<aTarget>.perform(<aSelector>, with: <anArgument>, afterDelay: someDelay)
You can cancel it with:
NSObject.cancelPreviousPerformRequests(withTarget: <aTarget>, selector: <aSelector>, object: <anArgument>)
only when all 3 things aTarget
, aSelector
and anArgument
match.
Please try something like this and check what you see:
class InputView: UIView {
var lastPerformArgument: NSString? = nil
func updateTable(text: String) {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(loadPlaces(text:)), object: lastPerformArgument)
lastPerformArgument = text as NSString
self.perform(#selector(loadPlaces(text:)), with: lastPerformArgument, afterDelay: 0.5)
}
//Private wrapper function
@objc private func loadPlaces(text: String) {
print("loading results for: \(text)")
// locator?.searchTextHasChanged(text: text)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With