Xcode NSTimer error: Unrecognized selector sent to class - ios

I have a file called Util.swift with the following
class foo: NSObject {
func sayHello() {
print("hello")
}
}
var globalTimer = NSTimer()
func startTheTimer() {
globalTimer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: foo.self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
}
When I call startTheTimer() in the viewDidLoad of any of my viewControllers, I get an error saying "unrecognized selector sent to class". I don't know what I have done wrong here.

class func sayHello() {
print("hello")
}
make this a class method please.

I have updated your code here:
class foo: NSObject {
class func sayHello() {
print("hello")
}
}
var globalTimer = NSTimer()
func startTheTimer() {
globalTimer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: foo.self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
}

Related

How can we define selector method in protocol extension in Swift 4.0

I want to to run a timer to show a toast message on the view controller but I am getting error: #objc can only be used with members of classes, #objc protocols, and concrete extensions of classes
#objc protocol Test: class {
var name: String { get }
func showToastMessage()
}
extension Test {
var timer: Timer {
return Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(showToastMessage), userInfo: nil, repeats: false)
}
#objc func showToastMessage() {
}
}

unrecognized selector sent to instance - Swift 3

I created a class based on this code: https://gist.github.com/etartakovsky/06b8c9894458a3ff1b14
When I try to instantiate the class and call the tick methods, I get "unrecognized selector sent to instance" error. I reviewed the code over and over but don't understand why this is happening, any advice is appreciated:
StopWatch Class source:
import Foundation
import QuartzCore
class StopWatch: NSObject{
private var displayLink: CADisplayLink!
private let formatter = DateFormatter()
var callback: (() -> Void)?
var elapsedTime: CFTimeInterval!
override init() {
super.init()
self.displayLink = CADisplayLink(target: self, selector: "tick:")
displayLink.isPaused = true
displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
self.elapsedTime = 0.0
formatter.dateFormat = "mm:ss,SS"
}
convenience init(withCallback callback: #escaping () -> Void) {
self.init()
self.callback = callback
}
deinit {
displayLink.invalidate()
}
func tick(sender: CADisplayLink) {
elapsedTime = elapsedTime + displayLink.duration
callback?()
}
func start() {
displayLink.isPaused = false
}
func stop() {
displayLink.isPaused = true
}
func reset() {
displayLink.isPaused = true
elapsedTime = 0.0
callback?()
}
func elapsedTimeAsString() -> String {
return formatter.string(from: Date(timeIntervalSinceReferenceDate:elapsedTime))
}
}
And here is the ViewController Code:
import UIKit
class ActivityViewController: UIViewController {
let stopwatch = StopWatch()
#IBOutlet weak var elapsedTimeLabel: UILabel!
func tick() {
elapsedTimeLabel.text = stopwatch.elapsedTimeAsString()
}
override func viewDidLoad() {
super.viewDidLoad()
tick()
stopwatch.callback = self.tick
stopwatch.start()
// Do any additional setup after loading the view.
}
}
In Swift 3 use the #selector syntax
self.displayLink = CADisplayLink(target: self, selector: #selector(tick))
In Swift 4 additionally you have to insert #objc at the beginning of the action
#objc func tick(...
Try two things. Use the new (and safer) selector syntax introduced by Swift 3:
CADisplayLink(target: self, selector: #selector(tick(sender:)))
and be sure to expose your tick method to Objective-C (the rules have changed in Swift 4):
#objc func tick(sender: CADisplayLink) {
...
}
To make it clear: unrecognized selector sent to instance is an error in MessagePassing scenario which means the desired selector which is:
func tick(sender: CADisplayLink) {...}
and has to receive the message is unrecognized.
It cannot be found because of the wrong way of addressing to it.
as other members said, you have to change your target selector by adding #selector(tick):
self.displayLink = CADisplayLink(target: self, selector: #selector(tick))
you can find more details about the error in this thread

Swift 2 - Timed Actions one second apart?

I'm trying to get output like so:
1 (then a one second delay)
Hello
2 (then a one second delay)
Hello
3 (then a one second delay)
Hello
But instead I get
1
2
3 (then a one second delay)
Hello
Hello
Hello
Here's my for loop invoking the NSTimer
var timer = NSTimer()
for i in 1...3 {
print("\(i)");
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(MainVPScreenViewController.printTest), userInfo: nil, repeats: false)
}
And here's the selector method:
func printTest() {
print("Hello")
}
Thanks in advance for any help you can provide
Try this solution without NSTimer:
var i = 1
func printHello() {
print(i)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
print("Hello")
i +=1
if i <= 3 {
printHello()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
printHello()
}
I need 2 NSTimers to do this, this is my approach
class ViewController: UIViewController {
var i = 1
override func viewDidLoad() {
super.viewDidLoad()
beginPrinting()
}
func beginPrinting() {
var timer2 = NSTimer()
if(i <= 100)
{
timer2 = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(self.printWithDelay), userInfo: nil, repeats: false)
}
}
func printWithDelay()
{
var timer = NSTimer()
print("\(i)");
i += 1
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(self.printTest), userInfo: nil, repeats: false)
}
func printTest() {
print("Hello")
beginPrinting()
}
}
Hope this helps you
Use timer with repeat to true. So in your view controller would be like this
var timer = NSTimer()
var counter = 0
var max = 10
let delay = 1 // in second
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self,
selector: #selector(self.printTest), userInfo: nil, repeats: true)
}
func printTest() {
counter += 1
print(counter)
print(hello)
if counter == maxNumber {
timer.invalidate()
}
}
This does it with repeat false, and is set up to be in a playground:
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
#objc class Foo: NSObject {
static var timer = NSTimer()
var i:Int
override init() {
Foo.timer = NSTimer()
i = 1
}
func schedule() {
print("\n\(i)");
i += 1
Foo.timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: #selector(printTest),
userInfo: nil,
repeats: false)
}
#objc func printTest() {
print("Hello")
if i < 5 {
schedule()
}
}
}
let bar = Foo()
bar.schedule()

Updating UILabel.text with a variable belonging to a Singleton

I have the following Singleton
class SharingManager{
var smallBigText : String = "KLANG!"
static let sharedInstance = SharingManager()
}
I use it to set the text of the following UILabel
#IBOutlet weak var kleinGrossLabel: UILabel!
I initialize its text here, in my ViewController:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.kleinGrossLabel.text = SharingManager.sharedInstance.smallBigText
}
I reset the SharingManager.sharedInstance.smallBigText in an instance method of my SoundEvent class:
class SoundEvent {
var text:String
var duration:Double
init(text: String, duration: Double){
self.text = text
self.duration = duration
}
func startEvent(){
SharingManager.sharedInstance.smallBigText = self.text
}
func getDuration() -> Double{
return self.duration
}
}
When I run the app, the UILabel text remains as "KLANG" and is never changed.
It should be changed when I call startEvent in the following function:
func playEvent(eventIndex : Int){
if (eventIndex < 2){
let currEvent = self.eventArray[eventIndex]
currEvent?.startEvent()
let nextIndex = eventIndex + 1
//NSTimer.scheduledTimerWithTimeInterval(0.4, target: SomeClass.self, selector: Selector("someClassMethod"), userInfo: nil, repeats: true)
NSTimer.scheduledTimerWithTimeInterval((currEvent?.duration)!, target: self, selector: Selector("playEvent:"), userInfo: NSNumber(integer: nextIndex), repeats: false)
}
else if (eventIndex==2){
self.eventArray[eventIndex]?.startEvent()
NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: Selector("sentenceDidFinish"), userInfo: nil, repeats: false)
}
else{
//Do Nothing
}
}
Which I call here in my ViewController
var s1:Sentence = Sentence(type: "S3")
s1.start()
Which in the Sentence class does this:
func start(){
self.playEvent(0)
}
Somehow it breaks this flow of logic, or if the expected sequence of events IS executing, then it follows that I am not actually changing the UILabel's text when I change the shared Singleton resource var smallBigText
For clarity here are all the main .swift
https://gist.github.com/anonymous/07542f638fc5b9a3c4e9
https://gist.github.com/anonymous/10f5f0deb03f9adc354c
https://gist.github.com/anonymous/94fda980836dc057b05b

How to make NSTimer for multiple different controllers? In swift

I make app on page-based.based. I have multiple different controllers. Every controller has NSTimer which turn on when the controller viewDidAppear(). I made that NSTimer turn off when controller is viewDidDisappear(). It works when I slowly slide between controllers but if I fast slide controller some timers don't turn off. I tried several different methods. The first method I post notification to the function which turn off timer but it also works when I slowly slide between controllers. The second method I created public class timer which I used for every controller it doesn't work for me. I don't know how can I make it. Please help me. I also have NSTimer in UIView which is located on UIViewController and I need that when UIViewController is disappear to turn off two timers. How can I make it?
My examples on ViewController
var timer: NSTimer!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
notificationCenter.postNotificationName("turnOnMemoryArc", object: nil)
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "showMemory", userInfo: nil, repeats: true)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(true)
if timer != nil {
timer.invalidate()
}
notificationCenter.postNotificationName("turnOffMemoryArc", object: nil)
}
This code in the view
override func drawRect(rect: CGRect) {
notificationCenter.addObserver(self, selector: "turnOnMemoryTimer", name: "turnOnMemoryArc", object: nil)
notificationCenter.addObserver(self, selector: "turnOffMemoryTimer", name: "turnOffMemoryArc", object: nil)
// timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawMemoryArc", userInfo: nil, repeats: true)
}
func turnOnMemoryTimer() {
print("memory timer is on")
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawMemoryArc", userInfo: nil, repeats: true)
}
func turnOffMemoryTimer() {
if timer != nil {
timer.invalidate()
}
print("Memory timer turned")
}
The second method
import Foundation
public class GlobalTimer {
public init() { }
public var controllerTimer: NSTimer!
public var viewTimer: NSTimer!
}
ViewController
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
notificationCenter.postNotificationName("turnOnBatteryArc", object: nil)
if GlobalTimer().controllerTimer != nil {
GlobalTimer().controllerTimer.invalidate()
GlobalTimer().controllerTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "batteryStateForLabel", userInfo: nil, repeats: true)
} else {
GlobalTimer().controllerTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "batteryStateForLabel", userInfo: nil, repeats: true)
}
}
UIView
func turnOnBatteryTimer() {
print("turnOnBatteryTimer arc")
if GlobalTimer().viewTimer != nil {
GlobalTimer().viewTimer.invalidate()
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
} else {
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
}
}
The main problem in your code is GlobalTimer(). This creates new GlobalTimer instance every single time you do write GlobalTimer().
Four different instance in the following piece of code - no shared state.
if GlobalTimer().viewTimer != nil {
GlobalTimer().viewTimer.invalidate()
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
} else {
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
}
You can fix it with singleton pattern ...
public class GlobalTimer {
public static let sharedInstance = GlobalTimer()
private init() { }
public var controllerTimer: NSTimer!
public var viewTimer: NSTimer!
}
... by replacing all GlobalTimer() occurences ...
if GlobalTimer().viewTimer != nil {
... with GlobalTimer.sharedInstace ...
if GlobalTimer.sharedInstance.viewTimer != nil {
Next step is ! removal, which is dangerous and can lead to crash if you don't know what you're doing.
public class GlobalTimer {
public static let sharedInstance = GlobalTimer()
private init() { } <-- avoid instantiation outside the file
public var controllerTimer: NSTimer? <-- was !
public var viewTimer: NSTimer? <-- was !
}
Remaining step is to replace your code using GlobalTimer ...
if GlobalTimer().viewTimer != nil {
GlobalTimer().viewTimer.invalidate()
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
} else {
GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
}
... with ...
GlobalTimer.sharedInstance.viewTimer?.invalidate()
GlobalTimer.sharedInstance.viewTimer = NSTimer.scheduled...
First line says - call invalidate() function on viewTimer if viewTimer != nil otherwise nothing happens (similar to nil messaging in ObjC, ...). This allows you to remove the if condition, because it invalidates timer or does nothing.
Second line just assigns new timer.
Try putting timer.invalidate() in the view will disappear instead of the did disappear. You shouldn't need the notifications at all when doing this.

Resources