Swift: Trying to target Class method as selector for NSTimer - swift

I am trying to use a class method of my class Sentence as the selector for my NSTimer. Here is my method, which incidentally is also the one which makes the NSTimer call :
func playEvent(eventIndex : Int){
if (eventIndex < 2){
let currEvent = self.eventArray[eventIndex]
currEvent?.startEvent()
let nextIndex = eventIndex + 1
NSTimer.scheduledTimerWithTimeInterval((currEvent?.duration)!, target: self, selector: "playEvent:sentence:", userInfo: NSNumber(integer: nextIndex), repeats: true)
}
else if (eventIndex==2){
self.eventArray[eventIndex]?.startEvent()
NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "sentenceDidFinish", userInfo: nil, repeats: true)
}
else{
//do nothing
}
}
Do I need to write self.playEvent in the selector or is that impossible to do?
Here is the whole class context:
https://gist.github.com/ebbnormal/e56b0b6ebedbe4474d01

your question/code is hard to follow, please add the class definition and reword the question.
UPDATE
class SomeClass: NSObject {
class func someClassMethod() {
print("called class method")
}
func someInstanceMethod() {
print("called instance method")
}
func setupInstanceMethodTimer() {
NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: Selector("someInstanceMethod"), userInfo: nil, repeats: true)
}
}
// setup timer for class method
NSTimer.scheduledTimerWithTimeInterval(0.4, target: SomeClass.self, selector: Selector("someClassMethod"), userInfo: nil, repeats: true)
// setup timer for instance method
let someInstance = SomeClass()
someInstance.setupInstanceMethodTimer()

Related

Not understanding how target, #selector() and #objc is being wrongfully used in this code

Could someone explain to me why this isn't working
class MyClass{
#objc func doSomething(){
print("I did something");
}
}
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: MyClass, selector: #selector(doSomething), userInfo: nil, repeats: true)
----EDIT--------
Found out that this works instead
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: MyClass(), selector: #selector(MyClass.doSomething), userInfo: nil, repeats: false)
Could someone explain why target needs to be MyClass() and not MyClass and why it needs to be MyClass.doSomething and not doSomething. Cos I thought the target becomes the scope for the selector
Correct Code
var timer : Timer?
func timerstarter(){
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(doSomething), userInfo: nil, repeats: true)
}
#objc func doSomething(){
print("I did something");
}

ScheduledTimer selector. Objective-c or Swift? [duplicate]

I tried
var timer = NSTimer()
timer(timeInterval: 0.01, target: self, selector: update, userInfo: nil, repeats: false)
But, I got an error saying
'(timeInterval: $T1, target: ViewController, selector: () -> (), userInfo: NilType, repeats: Bool) -> $T6' is not identical to 'NSTimer'
This will work:
override func viewDidLoad() {
super.viewDidLoad()
// Swift block syntax (iOS 10+)
let timer = Timer(timeInterval: 0.4, repeats: true) { _ in print("Done!") }
// Swift >=3 selector syntax
let timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
// Swift 2.2 selector syntax
let timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: #selector(MyClass.update), userInfo: nil, repeats: true)
// Swift <2.2 selector syntax
let timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
// must be internal or public.
#objc func update() {
// Something cool
}
For Swift 4, the method of which you want to get the selector must be exposed to Objective-C, thus #objc attribute must be added to the method declaration.
Repeated event
You can use a timer to do an action multiple times, as seen in the following example. The timer calls a method to update a label every half second.
Here is the code for that:
import UIKit
class ViewController: UIViewController {
var counter = 0
var timer = Timer()
#IBOutlet weak var label: UILabel!
// start timer
#IBAction func startTimerButtonTapped(sender: UIButton) {
timer.invalidate() // just in case this button is tapped multiple times
// start the timer
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
// stop timer
#IBAction func cancelTimerButtonTapped(sender: UIButton) {
timer.invalidate()
}
// called every time interval from the timer
func timerAction() {
counter += 1
label.text = "\(counter)"
}
}
Delayed event
You can also use a timer to schedule a one time event for some time in the future. The main difference from the above example is that you use repeats: false instead of true.
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
The above example calls a method named delayedAction two seconds after the timer is set. It is not repeated, but you can still call timer.invalidate() if you need to cancel the event before it ever happens.
Notes
If there is any chance of starting your timer instance multiple times, be sure that you invalidate the old timer instance first. Otherwise you lose the reference to the timer and you can't stop it anymore. (see this Q&A)
Don't use timers when they aren't needed. See the timers section of the Energy Efficiency Guide for iOS Apps.
Related
How to work with dates and time in Swift
Updated to Swift 4, leveraging userInfo:
class TimerSample {
var timer: Timer?
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 5.0,
target: self,
selector: #selector(eventWith(timer:)),
userInfo: [ "foo" : "bar" ],
repeats: true)
}
// Timer expects #objc selector
#objc func eventWith(timer: Timer!) {
let info = timer.userInfo as Any
print(info)
}
}
As of iOS 10 there is also a new block based Timer factory method which is cleaner than using the selector:
_ = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in
label.isHidden = true
}
Swift 5
I personally prefer the Timer with the block closure:
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (_) in
// TODO: - whatever you want
}
Swift 3, pre iOS 10
func schedule() {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 20, target: self,
selector: #selector(self.timerDidFire(timer:)), userInfo: nil, repeats: false)
}
}
#objc private func timerDidFire(timer: Timer) {
print(timer)
}
Swift 3, iOS 10+
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 20, repeats: false) { timer in
print(timer)
}
}
Notes
It needs to be on the main queue
Callback function can be public, private, ...
Callback function needs to be #objc
Check with:
Swift 2
var timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
Swift 3, 4, 5
var timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
You will need to use Timer instead of NSTimer in Swift 3.
Here is an example:
Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(YourController.update),
userInfo: nil,
repeats: true)
// #objc selector expected for Timer
#objc func update() {
// do what should happen when timer triggers an event
}
First declare your timer
var timer: Timer?
Then add line in viewDidLoad() or in any function you want to start the timer
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(action), userInfo: nil, repeats: false)
This is the func you will callback it to do something it must be #objc
#objc func action () {
print("done")
}
for swift 3 and Xcode 8.2
(nice to have blocks, but if You compile for iOS9 AND want userInfo):
...
self.timer = Timer(fireAt: fire,
interval: deltaT,
target: self,
selector: #selector(timerCallBack(timer:)),
userInfo: ["custom":"data"],
repeats: true)
RunLoop.main.add(self.timer!, forMode: RunLoopMode.commonModes)
self.timer!.fire()
}
func timerCallBack(timer: Timer!){
let info = timer.userInfo
print(info)
}
SimpleTimer (Swift 3.1)
Why?
This is a simple timer class in swift that enables you to:
Local scoped timer
Chainable
One liners
Use regular callbacks
Usage:
SimpleTimer(interval: 3,repeats: true){print("tick")}.start()//Ticks every 3 secs
Code:
class SimpleTimer {/*<--was named Timer, but since swift 3, NSTimer is now Timer*/
typealias Tick = ()->Void
var timer:Timer?
var interval:TimeInterval /*in seconds*/
var repeats:Bool
var tick:Tick
init( interval:TimeInterval, repeats:Bool = false, onTick:#escaping Tick){
self.interval = interval
self.repeats = repeats
self.tick = onTick
}
func start(){
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(update), userInfo: nil, repeats: true)//swift 3 upgrade
}
func stop(){
if(timer != nil){timer!.invalidate()}
}
/**
* This method must be in the public or scope
*/
#objc func update() {
tick()
}
}
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
And Create Fun By The Name createEnemy
fund createEnemy ()
{
do anything ////
}
In Swift 3 something like this with #objc:
func startTimerForResendingCode() {
let timerIntervalForResendingCode = TimeInterval(60)
Timer.scheduledTimer(timeInterval: timerIntervalForResendingCode,
target: self,
selector: #selector(timerEndedUp),
userInfo: nil,
repeats: false)
}
#objc func timerEndedUp() {
output?.timerHasFinishedAndCodeMayBeResended()
}
If you init method of timer
let timer = Timer(timeInterval: 3, target: self, selector: #selector(update(_:)), userInfo: [key : value], repeats: false)
func update(_ timer : Timer) {
}
then add it to loop using method other selector will not be called
RunLoop.main.add(timer!, forMode: .defaultRunLoopMode)
NOTE : If you are want this to repeat make repeats true and keep the reference of timer otherwise update method will not be called.
If you are using this method.
Timer.scheduledTimer(timeInterval: seconds, target: self, selector: #selector(update(_:)), userInfo: nil, repeats: true)
keep a reference for later use if repeats is true.
I tried to do in a NSObject Class and this worked for me:
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
print("Bang!") }
NSTimer has been renamed to Timer in Swift 4.2.
this syntax will work in 4.2:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(UIMenuController.update), userInfo: nil, repeats: true)

Swift 3 Timer in a Class not firing

I have a class:
class GameManager {...
and within it I have this func:
func startGame() {
msgTimer = Timer(timeInterval: 0.5, target: self, selector: #selector(typeMessage(_:)), userInfo: nil, repeats: true)
}
and the selector it calls:
#objc func typeMessage(_ sender:Timer) {
if textCount > strInitText.characters.count {
let strThisChar = strInitText[strInitText.index(strInitText.startIndex, offsetBy: textCount)]
strDisplayText = strDisplayText + String(strThisChar)
print(strDisplayText)
}
}
But the selector never gets called.
Change
msgTimer = Timer(timeInterval: 0.5, target: self, selector: #selector(typeMessage(_:)), userInfo: nil, repeats: true)
to
msgTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(typeMessage(_:)), userInfo: nil, repeats: true)
This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire.
And call it from the main thread as follows:
DispatchQueue.main.async { [weak self] in
self?.msgTimer = Timer(timeInterval: 0.5, target: self, selector: #selector(self.typeMessage(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(self.msgTimer, forMode: RunLoopMode.commonModes)
}
However, I recommend you to use scheduledTimer in this instance to remove this step:
Creates a timer and schedules it on the current run loop in the default mode.
Be sure to invalidate the timer when you are done with it as follows:
self.msgTimer.invalidate()

does not implement methodSignatureForSelector: Unrecognized selector (NSTimer)

I am using an NSTimer in my Swift app in the following way:
func playEvent(eventIndex : Int){
if (eventIndex < 2){
let currEvent = self.eventArray[eventIndex]
currEvent?.startEvent()
let nextIndex = eventIndex + 1
NSTimer.scheduledTimerWithTimeInterval((currEvent?.duration)!, target: self, selector: "playEvent(nextIndex)", userInfo: nil, repeats: true)
}
else if (eventIndex==2){
self.eventArray[eventIndex]?.startEvent()
NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "finishSentence", userInfo: nil, repeats: true)
}
else{
//do nothing
}
}
I think the problem may be either that I am not property calling the selector method with a parameter. If that is impossible, how could I work around this?
There is being called on the first NSTimer instance. Here is the full Swift class for reference: https://gist.github.com/ebbnormal/79941733b82f5fe58282
You can't pass nextIndex into a selector directly. One option for NSTimer could be to pass values via userInfo, for example:
NSTimer.scheduledTimerWithTimeInterval(1, target: self,
selector: "mySelector:",
userInfo: NSNumber(integer: 999),
repeats: true)
func mySelector(timer: NSTimer){
print(timer.userInfo)
}
A selector is represented by the method name
func playEvent()
selector: "playEvent"
If the method has one parameter, a colon is added
func playEvent(eventIndex : Int)
selector: "playEvent:"
If the method has multiple parameters, all parameter names are concatenated separated by colons
func playEvent(eventIndex : Int, flag: Bool)
selector: "playEvent:flag:"
Using NSTimer the direct parameter is designated to hold the timer instance.
func playEvent(timer : NSTimer)
You have to pass further parameters via the userInfo object of the timer.

Calling a method after a fixed period of time in swift [duplicate]

I tried
var timer = NSTimer()
timer(timeInterval: 0.01, target: self, selector: update, userInfo: nil, repeats: false)
But, I got an error saying
'(timeInterval: $T1, target: ViewController, selector: () -> (), userInfo: NilType, repeats: Bool) -> $T6' is not identical to 'NSTimer'
This will work:
override func viewDidLoad() {
super.viewDidLoad()
// Swift block syntax (iOS 10+)
let timer = Timer(timeInterval: 0.4, repeats: true) { _ in print("Done!") }
// Swift >=3 selector syntax
let timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
// Swift 2.2 selector syntax
let timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: #selector(MyClass.update), userInfo: nil, repeats: true)
// Swift <2.2 selector syntax
let timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
// must be internal or public.
#objc func update() {
// Something cool
}
For Swift 4, the method of which you want to get the selector must be exposed to Objective-C, thus #objc attribute must be added to the method declaration.
Repeated event
You can use a timer to do an action multiple times, as seen in the following example. The timer calls a method to update a label every half second.
Here is the code for that:
import UIKit
class ViewController: UIViewController {
var counter = 0
var timer = Timer()
#IBOutlet weak var label: UILabel!
// start timer
#IBAction func startTimerButtonTapped(sender: UIButton) {
timer.invalidate() // just in case this button is tapped multiple times
// start the timer
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
// stop timer
#IBAction func cancelTimerButtonTapped(sender: UIButton) {
timer.invalidate()
}
// called every time interval from the timer
func timerAction() {
counter += 1
label.text = "\(counter)"
}
}
Delayed event
You can also use a timer to schedule a one time event for some time in the future. The main difference from the above example is that you use repeats: false instead of true.
timer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
The above example calls a method named delayedAction two seconds after the timer is set. It is not repeated, but you can still call timer.invalidate() if you need to cancel the event before it ever happens.
Notes
If there is any chance of starting your timer instance multiple times, be sure that you invalidate the old timer instance first. Otherwise you lose the reference to the timer and you can't stop it anymore. (see this Q&A)
Don't use timers when they aren't needed. See the timers section of the Energy Efficiency Guide for iOS Apps.
Related
How to work with dates and time in Swift
Updated to Swift 4, leveraging userInfo:
class TimerSample {
var timer: Timer?
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 5.0,
target: self,
selector: #selector(eventWith(timer:)),
userInfo: [ "foo" : "bar" ],
repeats: true)
}
// Timer expects #objc selector
#objc func eventWith(timer: Timer!) {
let info = timer.userInfo as Any
print(info)
}
}
As of iOS 10 there is also a new block based Timer factory method which is cleaner than using the selector:
_ = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in
label.isHidden = true
}
Swift 5
I personally prefer the Timer with the block closure:
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { (_) in
// TODO: - whatever you want
}
Swift 3, pre iOS 10
func schedule() {
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 20, target: self,
selector: #selector(self.timerDidFire(timer:)), userInfo: nil, repeats: false)
}
}
#objc private func timerDidFire(timer: Timer) {
print(timer)
}
Swift 3, iOS 10+
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(withTimeInterval: 20, repeats: false) { timer in
print(timer)
}
}
Notes
It needs to be on the main queue
Callback function can be public, private, ...
Callback function needs to be #objc
Check with:
Swift 2
var timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
Swift 3, 4, 5
var timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
You will need to use Timer instead of NSTimer in Swift 3.
Here is an example:
Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(YourController.update),
userInfo: nil,
repeats: true)
// #objc selector expected for Timer
#objc func update() {
// do what should happen when timer triggers an event
}
First declare your timer
var timer: Timer?
Then add line in viewDidLoad() or in any function you want to start the timer
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(action), userInfo: nil, repeats: false)
This is the func you will callback it to do something it must be #objc
#objc func action () {
print("done")
}
for swift 3 and Xcode 8.2
(nice to have blocks, but if You compile for iOS9 AND want userInfo):
...
self.timer = Timer(fireAt: fire,
interval: deltaT,
target: self,
selector: #selector(timerCallBack(timer:)),
userInfo: ["custom":"data"],
repeats: true)
RunLoop.main.add(self.timer!, forMode: RunLoopMode.commonModes)
self.timer!.fire()
}
func timerCallBack(timer: Timer!){
let info = timer.userInfo
print(info)
}
SimpleTimer (Swift 3.1)
Why?
This is a simple timer class in swift that enables you to:
Local scoped timer
Chainable
One liners
Use regular callbacks
Usage:
SimpleTimer(interval: 3,repeats: true){print("tick")}.start()//Ticks every 3 secs
Code:
class SimpleTimer {/*<--was named Timer, but since swift 3, NSTimer is now Timer*/
typealias Tick = ()->Void
var timer:Timer?
var interval:TimeInterval /*in seconds*/
var repeats:Bool
var tick:Tick
init( interval:TimeInterval, repeats:Bool = false, onTick:#escaping Tick){
self.interval = interval
self.repeats = repeats
self.tick = onTick
}
func start(){
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(update), userInfo: nil, repeats: true)//swift 3 upgrade
}
func stop(){
if(timer != nil){timer!.invalidate()}
}
/**
* This method must be in the public or scope
*/
#objc func update() {
tick()
}
}
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
And Create Fun By The Name createEnemy
fund createEnemy ()
{
do anything ////
}
In Swift 3 something like this with #objc:
func startTimerForResendingCode() {
let timerIntervalForResendingCode = TimeInterval(60)
Timer.scheduledTimer(timeInterval: timerIntervalForResendingCode,
target: self,
selector: #selector(timerEndedUp),
userInfo: nil,
repeats: false)
}
#objc func timerEndedUp() {
output?.timerHasFinishedAndCodeMayBeResended()
}
If you init method of timer
let timer = Timer(timeInterval: 3, target: self, selector: #selector(update(_:)), userInfo: [key : value], repeats: false)
func update(_ timer : Timer) {
}
then add it to loop using method other selector will not be called
RunLoop.main.add(timer!, forMode: .defaultRunLoopMode)
NOTE : If you are want this to repeat make repeats true and keep the reference of timer otherwise update method will not be called.
If you are using this method.
Timer.scheduledTimer(timeInterval: seconds, target: self, selector: #selector(update(_:)), userInfo: nil, repeats: true)
keep a reference for later use if repeats is true.
I tried to do in a NSObject Class and this worked for me:
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
print("Bang!") }
NSTimer has been renamed to Timer in Swift 4.2.
this syntax will work in 4.2:
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(UIMenuController.update), userInfo: nil, repeats: true)