Swift NSTimer unrecognized selector sent to instance timerFireMethod - ios

I'm writing some timer code in Swift for iOS 9.2
I have the latest iOS 9.2 docs downloaded through xcode
They show
(void)timerFireMethod:(NSTimer *)timer
But this will not work.
If I use signatures like these
func timerFire(timer : NSTimer?)
func timerFire(timer : NSTimer)
Then I get the error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DSP1.PlayManager timerFire]: unrecognized selector sent to instance
The only thing I can get to work is a call signature like this
func play(sound : String)
{
bsound = theLM?.getPlayer(sound)
bsound?.delegate = self
bsound?.play()
stimer = NSTimer(timeInterval: 1.0, target: self, selector: Selector("timerFire"), userInfo: self, repeats: true)
NSRunLoop.currentRunLoop().addTimer(stimer!, forMode: NSDefaultRunLoopMode)
}
// Docs say signature should be (void)timerFireMethod:(NSTimer *)timer. Docs are wrong
func timerFire()
{
print("Player at: \(bsound?.currentTime) out of \(bsound?.duration) seconds");
}
But this does not match what the latest iOS 9.2 docs downloaded through XCode say should work.
Am I doing this right?
Why do the freshly loaded iOS 9.2 docs seem to have the wrong signature?
What are other people reading for accurate documentation for Swift iOS programming?
(Edited to be more clear that the callback signatures listed in the docs fail to work at runtime)
Answer:
In the call to NSTimer, if your function name passed in as Selector has a trailing colon, it means you want the timer passed as an argument to your method. No colon means you don't want the timer passed as an argument.
NSTimer(timeInterval: 1.0, target: self, selector: Selector("timerFire"), userInfo: self, repeats: true)
func timerFire()
OR
NSTimer(timeInterval: 1.0, target: self, selector: Selector("timerFire:"), userInfo: self, repeats: true)
func timerFire(timer : NSTimer)
The documentation for NSTimer mentions this for the selector argument, but is far from clear. "The selector should have the following signature: timerFireMethod: (including a colon to indicate that the method takes an argument). "

You've made a simple, yet common mistake.
Your method signature should be:
func timerFire(timer: NSTimer) {}
And your timer setup should be:
NSTimer(timeInterval: 1.0, target: self, selector: "timerFire:", userInfo: nil, repeats: true)
The mistake is that you're missing the colon in the selector name. timerFire is different from timerFire:. Skip the colon and it'll look for for a method like this:
func timerFire() {}
Without the NSTimer parameter. It's best though to include the parameter, and thus the colon, so that you can confirm the timer you get is the one you expect.
The same is true for notifications. If you're using Notification Center, include the colon, and the Notification object in the method.

Your code seems to be ok. The selector for your timer does not have a predefined signature, you can all it whatever you like as long a you have a method in your class with that name.
func play(sound : String) {
// ....
stimer = NSTimer(timeInterval: 1.0, target: self, selector: "methodToRunOnTimerTick", userInfo: self, repeats: true)
NSRunLoop.currentRunLoop().addTimer(stimer!, forMode: NSDefaultRunLoopMode)
}
func methodToRunOnTimerTick() {
print("Player at: \(bsound?.currentTime) out of \(bsound?.duration) seconds");
}
One more thing to remember is that the method you decide to use can also receive the timer as a parameter when it is being called. This case would look like this:
func play(sound : String) {
// ....
stimer = NSTimer(timeInterval: 1.0, target: self, selector: "methodToRunOnTimerTick:", userInfo: self, repeats: true)
NSRunLoop.currentRunLoop().addTimer(stimer!, forMode: NSDefaultRunLoopMode)
}
func methodToRunOnTimerTick(timer: NSTimer) {
print("Player at: \(bsound?.currentTime) out of \(bsound?.duration) seconds");
}
So you implement the timer the right way, the method does not need a special signature. Let me know if you need more help. Good luck with your project!

Related

Timer.scheduledTimer does not work in Swift 3

I want to call the method func adjustmentBestSongBpmHeartRate() every 1.1 second. I used Timer, but it doesn't work. I have read the document and found a lot of sample code, it still does work! Is there anything I missed?
timer = Timer.scheduledTimer(timeInterval: 1.1, target: self, selector: #selector(self.adjustmentBestSongBpmHeartRate), userInfo: nil, repeats: false)
timer.fire()
func adjustmentBestSongBpmHeartRate() {
print("frr")
}
I found that creating the timer in an OperationQueue Operation did not work. I assume this is because there is no runloop.
Therefore, the following code fixed my problem:
DispatchQueue.main.async {
// timer needs a runloop?
self.timeoutTimer = Timer.scheduledTimer(timeInterval: self.timeout, target: self, selector: #selector(self.onTimeout(_:)), userInfo: nil, repeats: false)
}
Timer methods with a selector are supposed to have one parameter: The timer itself. Thus your code should really look like this: 1
Timer.scheduledTimer(timeInterval: 1.1,
target: self,
selector: #selector(self.adjustmentBestSongBpmHeartRate(_:),
userInfo: nil,
repeats: false)
#objc func adjustmentBestSongBpmHeartRate(_ timer: Timer) {
print("frr")
}
Note that if your app only runs on iOS >= 10, you can use the new method that takes a block to invoke rather than a target/selector. Much cleaner and more type-safe:
class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: #escaping (Timer) -> Void) -> Timer
That code would look like this:
timer = Timer.scheduledTimer(withTimeInterval: 1.1,
repeats: false) {
timer in
//Put the code that be called by the timer here.
print("frr")
}
Note that if your timer block/closure needs access to instance variables from your class you have to take special care with self. Here's a good pattern for that sort of code:
timer = Timer.scheduledTimer(withTimeInterval: 1.1,
repeats: false) {
//"[weak self]" creates a "capture group" for timer
[weak self] timer in
//Add a guard statement to bail out of the timer code
//if the object has been freed.
guard let strongSelf = self else {
return
}
//Put the code that be called by the timer here.
print(strongSelf.someProperty)
strongSelf.someOtherProperty = someValue
}
Edit (updated 15 December)
1: I should add that the method you use in the selector has to use Objective-C dynamic dispatch. In Swift 4 and later, the individual methods you reference must be tagged with the #objc tag. In previous versions of Swift you could also declare the entire class that defines the selector with the #objc qualifier, or you could make the class that defined the selector a subclass of NSObject or any class that inherits from NSOBject. (It's quite common to define the method the timer calls inside a UIViewController, which is a subclass of NSObject, so it used to "just work".
Swift 3
In my case it worked after I added to my method the #obj prefix
Class TestClass {
private var timer: Timer?
func start() {
guard timer == nil else { return }
timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(handleMyFunction), userInfo: nil, repeats: false)
}
func stop() {
guard timer != nil else { return }
timer?.invalidate()
timer = nil
}
#objc func handleMyFunction() {
// Code here
}
}
Try this -
if #available(iOS 10.0, *) {
self.timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: { _ in
self.update()
})
} else {
self.timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(self.update), userInfo: nil, repeats: false)
}
Mostly the problem must have been because of iOS version of mobile.
Swift 5, Swift 4 Simple way only call with Dispatch Queue Async
DispatchQueue.main.async
{
self.andicator.stopAnimating()
self.bgv.isHidden = true
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { _ in
obj.showAlert(title: "Successfully!", message: "Video save successfully to Library directory.", viewController: self)
})
}
I have solved the question asked by myself.
I'm using apple watch to control my iphone app.
I try to press a button on apple watch to present a new viewcontroller on iphone.
When I write Timer in override func viewDidLoad(), Timer doesn't work. I move Timer to override func viewWillAppear() it works.
I think maybe there's something wrong with controlling by apple watch
I found that if you try to initialize the timer directly at the class-level, it won't work if you're targeting a selector in that same class. When it fires, it can't find the selector.
To get around this, I only initialize the timer after the object containing the selector has been initialized. If it's in the same class, put the initialization code in the ViewDidLoad or similar. Just not in the initializer. Then it will work. No dispatch queue needed.
Also, you do not need to use a selector that accepts the timer as a parameter. You can, but contrary to the answer with a ton of votes, that's not actually true, or more specifically, it works fine for me without it, just as you have it without it.
By the way, I think the reason the dispatch queue worked is because you're forcing the timer to be created after the object was initializing, confirming my above statement.
let timer:Timer?
override func viewDidLoad(){
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1.1, target: self, selector: #selector(adjustmentBestSongBpmHeartRate), userInfo: nil, repeats: false)
timer.fire()
}
func adjustmentBestSongBpmHeartRate() {
print("frr")
}
Note: This is code typed from memory, not copied from Xcode so it may not compile, but hopefully you get the idea.
Swift3
var timer = Timer()
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.compruebaConexion), userInfo: nil, repeats: true)
my two cents.
I read about "didLoad" and when invoking it.
so we can use a delay:
class ViewController: UIViewController {
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
startTimer()
}
final func killTimer(){
self.timer?.invalidate()
self.timer = nil
}
final private func startTimer() {
// make it re-entrant:
// if timer is running, kill it and start from scratch
self.killTimer()
let fire = Date().addingTimeInterval(1)
let deltaT : TimeInterval = 1.0
self.timer = Timer(fire: fire, interval: deltaT, repeats: true, block: { (t: Timer) in
print("hello")
})
RunLoop.main.add(self.timer!, forMode: RunLoopMode.commonModes)
}

Swift 3.0: Timer not firing for target other than self

Timer.scheduledTimer(timeInterval: 5.0, target:self.notificationView, selector: #selector(NotificationView.self.timerFired(_:)), userInfo: nil, repeats: false)
func timerFired(_ timer: Timer) {
print("Timer Fired")
}
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue timerFired:]: unrecognized selector sent to instance 0x7fc0baf46f60'
I don't understand where is wrong? If target is self then everything works fine.
The problem is with your selector syntax it will be like this.
#selector(NotificationView.timerFired(_:))
Note : self is for current ViewController if you want to set action for another then you need to specify the class name.method in your case it is NotificationView.timerFired.
I try the following code and NotificationView.timerFired is triggered:
class NotificationView {
#objc func timerFired(_ timer: Timer) {
print("Timer Fired")
}
}
class ViewController: UIViewController {
let notificationView = NotificationView()
override func viewDidLoad() {
super.viewDidLoad()
Timer.scheduledTimer(
timeInterval: 5.0,
target:self.notificationView,
selector: #selector(NotificationView.timerFired(_:)),
userInfo: nil,
repeats: false
)
}
}
Check this part of your error message:
[_SwiftValue timerFired:]
timerFired: is an Objective-C style notation of the selector. Seems your #selector(...) is working. (Though not recommended...)
_SwiftValue is a class name of the object being the target of the selector. This means your target target:self.notificationView is converted to _SwiftValue.
This may happen when you declare your notificationView as Optional or implicitly unwrapped Optional. If so, try this:
Timer.scheduledTimer(timeInterval: 5.0, target: self.notificationView!, selector: #selector(NotificationView.timerFired(_:)), userInfo: nil, repeats: false)
(Please do not miss the ! after self.notificationView.)
The code below worked for me (in playground/swift 3) :
class SomeClass {
#objc public func timerFired(_ timer: Timer) {
print("Timer Fired")
}
}
let s = SomeClass()
Timer.scheduledTimer(timeInterval: 5.0, target:s, selector: #selector(s.timerFired(_:)), userInfo: nil, repeats: false).fire()
//This also will work
//Timer.scheduledTimer(timeInterval: 5.0, target:s, selector: #selector(SomeClass.timerFired(_:)), userInfo: nil, repeats: false).fire()

Swift selector - unrecognized selector sent to instance

I have a function:
func runRockRotation(rockSprite: SKSpriteNode){
startRockRotationAnimation(rockSprite, isRock: true)
}
When I call it like this:
runRockRotation(rock)
it works, but I can't seem to be able to put it inside a NSTimer selector.
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "runRockRotation:", userInfo: rock, repeats: false)
Read a lot of forums, tried this:
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "runRockRotation()", userInfo: rock, repeats: false)
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "runRockRotation(_)", userInfo: rock, repeats: false)
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "runRockRotation", userInfo: rock, repeats: false)
Also, tried without rock, using nil, but nothing seems to work.
Every time I get:
2015-04-10 15:49:03.830 Meh[1640:218030] -[__NSCFTimer runAction:completion:]: unrecognized selector sent to instance 0x174166840
2015-04-10 15:49:03.832 Meh[1640:218030] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFTimer runAction:completion:]: unrecognized selector sent to instance 0x174166840'
How do I call my function in a selector with a parameter? I know how to do that in Objective C, but can't seem to do it in Swift. All help will be appreciated.
Check the documentation for scheduledTimerWithTimeInterval
You will see that
The selector should have the following signature: timerFireMethod:
(including a colon to indicate that the method takes an argument).
The
timer passes itself as the argument, thus the method would adopt the
following pattern:
- (void)timerFireMethod:(NSTimer *)timer
Your function doesn't match this pattern, so the appropriate selector cannot be found.
Use something like -
func runRockRotationForTimer(_ timer: NSTimer){
self.runRockRotation(timer.userInfo as? SKSpriteNode)
timer.invalidate();
}
and schedule it using
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "runRockRotationForTimer:", userInfo: rock, repeats: false)
Also helps to make sure the target object (self in your case) is a subclass of NSObject
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: <THIS MUST BE NSOBJECT SUBCLASS>, selector: "runRockRotation:", userInfo: rock, repeats: false)

Extra argument 'selector' in call error

class ViewController: UIViewController {
func ChangePage()
{
NSLog("Hej")
}
var timers = NSTimer(NSTimeInterval(0.5), target:self, selector: "ChangePage", userInfo: nil, repeats: true)
}
I get the following error from Xcode 6:
Extra Argument 'selector' in call
I've tried several configurations, does it have something to do with where in the code it's placed?
You might want to use:
var timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "changePage", userInfo: nil, repeats: true)
This returns a timer that is already added to the run loop and fires automatically.
To stop the timer to fire, you must invalidate it like this
timer.invalidate()
You should add timeInterval in the constructor like:
NSTimer(timeInterval: NSTimeInterval(0.5), target:self, selector: "ChangePage", userInfo: nil, repeats: true)
And yes, it does matter where you put. The problem is, that timers is a property, and it is created before the initialization. So when it is created, self is not existing, but you refer to it, and that causes the problem.

Can an NSTimer take multiple selectors?

I'm trying to use an NSTimer in my app, and was wondering if it's possible to call two methods when the timer fires.
Here's the code:
gameTimer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector:
Selector("gameMovement" && "fireBullet"), userInfo: nil, repeats: true)
I'm getting an error saying there are two arguments in the Selector.
Nope. You would call just one method that delegates to all the things you want.
func someFunc() {
gameTimer = NSTimer.scheduledTimerWithTimeInterval(
0.01,
target: self,
selector: Selector("timerFired"),
userInfo: nil,
repeats: true
)
}
func timerFired() {
gameMovement()
fireBullet()
}
This is a more maintainable pattern anyway, as it's easier to see how your code flows.

Resources