Repeat function using NSTimer fails with target: self in Swift - swift

I found this question and tried to copy the code to my Xcode project, but I'm getting the following error message.
error: use of unresolved identifier 'self'
What's the right way?
EDIT: Here the code, tested in a playground:
//: Playground - noun: a place where people can play
import Cocoa
import Foundation
func sayHello() {
print("hello World")
}
var SwiftTimer = NSTimer()
SwiftTimer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: Selector("sayHello"), userInfo: nil, repeats: true)

Usually using uppercase to the properties name it's considered as a bad attitude, you should use swiftTimer.
These expressions are not allowed ad the top level:
var swiftTimer = NSTimer()
swiftTimer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
You must put it in a function like for example:
override func viewDidLoad() {
super.viewDidLoad()
var swiftTimer = NSTimer()
swiftTimer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
}

self refers to the object for which a method (a function defined within a class) is defined, so can only be used in a method. (It is the equivalent of this in C++/Java/Javascript.) In your code, sayHello() is a global function, not a method, so there is no self to refer to.
In order for the scheduledTimerWithTimeInterval() function call to work with those arguments, it must be called within an instance method so that there is a self, and that class must have a method named sayHello().
You could also change the target: to another object as long as that object has a sayHello() method.

Basically an asynchronous timer doesn't work in a Playground and a since the top level of a Playground isn't a class there is no self property.
To test NSTimer in a Playground
Wrap the timer in a class.
Import XCPlaygound.
Add XCPlaygroundPage.currentPage.needsIndefiniteExecution = true to enable support of asynchronous tasks.

Related

Swift Runtime exception: unrecognized selector

In my ViewController class, I have a function:
func updateTimes() {
// (code)
}
I create a timer:
class ViewController: NSViewController {
var timer = Timer.scheduledTimer(timeInterval: 5,
target: self,
selector:
#selector(ViewController.updateTimes),
userInfo: nil,
repeats: true)
The compiler is happy with this. At runtime, when the timer fires, I get an exception:
unrecognized selector sent to instance 0x6000000428b0
Am I doing something obviously wrong?
As I wrote as a comment on NaGib ToroNgo's answer, he has given us a nice suggestion.
The selector may not be sent to the instance of ViewController.
I guess the ViewController would be taking this form:
class ViewController: UIViewController {
var timer = Timer.scheduledTimer(timeInterval: 5,
target: self,
selector: #selector(ViewController.updateTimes),
userInfo: nil,
repeats: true)
//...(Other property declarations or method definitions)...
func updateTimes() {
// (code)
}
}
The variable timer is declared as an instance property, and self is used in an initial value of timer. (In some old versions of Swift, this sort of usage caused error, so I was thinking that this line exists in any of the methods.)
In the current version of Swift (tested with Swift 3.1/Xcode 8.3.3), the code above does not cause error, but self is interpreted as a method reference of self() method declared in NSObjectProtocol. So, Selector("updateTimes") is sent to the closure representing the method reference (curried function), not to the instance of the ViewController.
The closure does not have a method named updateTimes, which caused the exception:
unrecognized selector sent to instance
Move the initial value code into some instance context, and then self represents the instance of the ViewController:
class ViewController: UIViewController {
var timer: Timer? //<- Keep `timer` as an instance property, but move the initial value code into `viewDidLoad()`.
//...(Other property declarations or method definitions)...
override func viewDidLoad() {
super.viewDidLoad()
//Do initialize the timer in the instance context.
timer = Timer.scheduledTimer(timeInterval: 5,
target: self,
selector: #selector(self.updateTimes),
userInfo: nil,
repeats: true)
//...
}
//In Swift 3, `#objc` is not needed, just for a preparation for Swift 4
#objc func updateTimes() {
// (code)
}
}
I believe this does not cause unrecognized selector exception.
The code you have provided seems perfect. I think the problem is, somehow your view controller is getting released or having dangling pointer.
Its time to say good bye to selectors!!! use the below code
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { (timer) in
// check self for nil before using
}

Cannot create Struct with CGRect based on super.view.frame [duplicate]

Just a simple task, but I'm in trouble. Trying to make a different way but it fails.
How to init NSTimer with declared previously variable? Neither var nor let helps.
The initial value of a property (in your case: timer) cannot depend on another property of the class (in your case: interval).
Therefore you have to move the assigment timer = NSTimer(interval, ...) into a method of the
class, e.g. into viewDidLoad. As a consequence, timer has to be defined as an
optional or implicitly unwrapped optional.
Note also that Selector(...) takes a literal string as argument, not the method itself.
So this should work:
class ViewController: UIViewController {
var interval : NSTimeInterval = 1.0
var timer : NSTimer!
func timerRedraw() {
}
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer(timeInterval: interval, target: self, selector: Selector("timerRedraw"), userInfo: nil, repeats: true)
// ...
}
// Other methods ...
}
Try:
var interval:NSTimeInterval = 1.0
var timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "timerRedraw:", userInfo: nil, repeats: true)
pro-tip and hopefully an appreciated FYI: Swift functions should also start with lower case letters (i.e. "timerRedraw").

NSTimer Swift difficulty

I am trying to create a NSTimer so that I can move a UIImageView down but
The NSTImer is having difficulty, saying first that this.
var timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target:self(), selector: Selector ("mrockdown"), userInfo: nil, repeats: true)
is missing argument for parameter #1 in call. But when I remove the brackets from the target:self() it tells me
Cannot invoke 'scheduledTimerWIthTimerInterval' with an argument list of type '(Double, target: ViewController -> () -> ViewController, selector: Selector, userinfo: nil, repeates Bool.
What should I do?
The problem has to do with where you are saying this. It looks like you are trying to say this as part of a property declaration:
class ViewController {
var timer = ...
// ...
}
But you can't do that, because there is no self as far as a stored property is concerned. You need to declare the timer as an Optional and then initialize it later:
class ViewController {
var timer = NSTimer!
func someMethod {
timer = ...
}
}
Then you will remove the parentheses (they are wrong) and everything will compile just fine.
You can do this inside the function where you want to trigger the timer (i.e. viewDidLoad or some IBAction)
_ = Timer.scheduledTimer(timeInterval: yourInterval, target: self, selector: #selector(yourFunction), userInfo: nil, repeats: true)
That being said, to animate a view you should use this instead:
UIView.animate(withDuration: yourDuration) {
// set yourView's final position here
}

Swift dictionary with String key and UIImageView as value [duplicate]

Just a simple task, but I'm in trouble. Trying to make a different way but it fails.
How to init NSTimer with declared previously variable? Neither var nor let helps.
The initial value of a property (in your case: timer) cannot depend on another property of the class (in your case: interval).
Therefore you have to move the assigment timer = NSTimer(interval, ...) into a method of the
class, e.g. into viewDidLoad. As a consequence, timer has to be defined as an
optional or implicitly unwrapped optional.
Note also that Selector(...) takes a literal string as argument, not the method itself.
So this should work:
class ViewController: UIViewController {
var interval : NSTimeInterval = 1.0
var timer : NSTimer!
func timerRedraw() {
}
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer(timeInterval: interval, target: self, selector: Selector("timerRedraw"), userInfo: nil, repeats: true)
// ...
}
// Other methods ...
}
Try:
var interval:NSTimeInterval = 1.0
var timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "timerRedraw:", userInfo: nil, repeats: true)
pro-tip and hopefully an appreciated FYI: Swift functions should also start with lower case letters (i.e. "timerRedraw").

NSTimer.scheduledTimerWithTimeInterval in Swift Playground

All the examples I've seen on using the "NSTimer.scheduledTimerWithTimeInterval" within Swift show using the "target: self" parameter, but unfortunately this doesn't work in Swift Playgrounds directly.
Playground execution failed: <EXPR>:42:13: error: use of unresolved
identifier 'self'
target: self,
Here's an example referenced above that results in the error:
func printFrom1To1000() {
for counter in 0...1000 {
var a = counter
}
}
var timer = NSTimer.scheduledTimerWithTimeInterval(0,
target: self,
selector: Selector("printFrom1To1000"),
userInfo: nil,
repeats: false
)
timer.fire()
You really should not be using NSTimer these days. It's consumes a lot of resources, causes unnecessary battery drain, and the API lends itself to ugly code.
Use dispatch_after() instead:
dispatch_after(0, dispatch_get_main_queue()) { () -> Void in
for counter in 0...1000 {
var b = counter
}
}
Of course, since the timer will fire after playground does it's stuff you will need an equivalent of timer.fire() to force the code to execute immediately instead of after a 0 second delay. Here's how that works:
let printFrom1To1000 = { () -> Void in
for counter in 0...1000 {
var b = counter
}
}
dispatch_after(0, dispatch_get_main_queue(), printFrom1To1000)
printFrom1To1000()
To get this to run directly within a Swift Playground, you need to embed the printFrom1To1000 function within a class and then set an instance of that class to the "target:" parameter instead of using "self".
Here's a full working example:
class myClass: NSTimer{
func printFrom1To1000() {
for counter in 0...1000 {
var b = counter
}
}
}
let myClassInstance = myClass()
var timer = NSTimer.scheduledTimerWithTimeInterval(0,
target: myClassInstance,
selector: Selector("printFrom1To1000"),
userInfo: nil,
repeats: false
)
timer.fire()
If you already have an object you are referencing (i.e., updating a label), you can extend that type and use that function as the Selector. I find this easier than creating a whole new class and instantiating a new object from it.
extension SKLabelNode {
func updateMe() {
count++
label.text = "\(count)"
}
}
var timer = NSTimer.scheduledTimerWithTimeInterval(0.25,
target: label,
selector: Selector("updateMe"),
userInfo: nil,
repeats: true)
timer.fire()