Swift- NSTimer crashing app - ios

In Xcode I have this:
import UIKit
import AVFoundation
import Foundation
var position = 0
var gameTimer = NSTimer()
class ViewController: UIViewController {
#IBAction func button(sender: AnyObject) {
gameTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "runTimedCode:", userInfo: nil, repeats: true)
func runTimedCode() {
position = Int(arc4random_uniform(11))
}}}
When I run this app it crashes and returns the error: Thread 1: signal SIGABRT.
I have run the script without the NSTimer and it works perfectly.
I have also run it with and without the colon and it returns the same result.

Two issues:
Put func runTimedCode() out of the scope of #IBAction button(). Selector / target methods must be on the top level of the class.
Either remove the colon of runTimedCode: or declare runTimedCode as runTimedCode(timer: NSTimer). Each colon in a Selector represents one parameter.

You have a couple of issues:
You have defined your runTimedCode function inside your button function rather than as an instance function
You have specified the correct selector signature runTimedCode: (with the colon) but you have not specified the NSTimer argument that will be sent to this function (which is what is indicated by the : in the selector). You want:
import UIKit
import AVFoundation
import Foundation
var position = 0
var gameTimer : NSTimer? // Don't assign a value just to keep the compiler happy - if the variable is an optional declare it as such
class ViewController: UIViewController {
#IBAction func button(sender: AnyObject) {
self.gameTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "runTimedCode:", userInfo: nil, repeats: true)
}
func runTimedCode(timer:NSTimer) {
position = Int(arc4random_uniform(11))
}
}

Related

Sending an NSTimer target message to a different class; extracting its userInfo parameter

I am using an NSTimer to send a message on an interval. Here is the code :
{
// ....
var params : [String] = []
params.append(conversion)
params.append(message)
let timer = NSTimer(fireDate: date, interval: 60, target: self, selector: Selector("importTextMessage.sendMessage:"), userInfo: params, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
// ...
}
func sendMessage(params: [String]){ ...}
I have also tried changing to the Swift 2.2 syntax:
let timer = NSTimer(fireDate: date, interval: 60, target: self, selector: #selector(importTextMessage.sendMessage(_:)), userInfo: params, repeats: true)
but that does not change anything.
From every other question posted about the "Unrecognized selector sent to instance", the response is "include a colon in the Selector so that it knows to grab arguments from UserInfo", but I have included that, and can't figure out what is wrong.
What to Note:
The parameters for the function and the parameters passed in through NSTimer userInfo DO match up. They are both arrays of strings.
If it means anything, the code is failing while sendMessage is being called. It does not actually make it to sendMessage.
I am getting an odd warning saying that "String literal is not a valid objective-c selector"
I tried changing my code for my sendMessage to take the Timer as an argument as 1 user suggested : func sendMessage(timer: NSTimer){but that still gives the same error.
Thank you for your help in advance, I do appreciate it.
EDIT: here is the function that runs the timer: //Save all of the data
#IBAction func saveText(sender: AnyObject) {
var phone : Double
var active : Int
var frequency : Double
var message : String
var date : NSDate
phone = Double(currentNumber)!
active = 1
message = myTextView.text
date = myDatePicker.date
switch self.frequency.selectedRowInComponent(0) {
case 0:
frequency = 1
case 1:
frequency = 3
case 2:
frequency = 6
case 3:
frequency = 24
case 4:
frequency = 168
case 5:
frequency = 744
default:
frequency = 8760
}
importTextMessage.seedMessage(phone, active: active, frequency: frequency, message: message, date: date)
print(date)
let conversion : String = "+1" + String(Int(phone))
//importTextMessage.sendMessage("Ryan", to: conversion, message: message)
var params : [String] = []
params.append(conversion)
params.append(message)
//importTextMessage.sendMessage(params)
let timer = NSTimer(fireDate: date, interval: 60, target: self, selector: #selector(importTextMessage.sendMessage(_:)), userInfo: params, repeats: true)
//
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
EDIT: Exact error mssg:
unrecognized selector sent to instance 0x7ffe6b915460
2016-07-25 13:26:09.092 Harass Your Kate[53524:9000621] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Harass_Your_Kate.AddMessageViewController sendMessage:]: unrecognized selector sent to instance 0x7ffe6b915460
From doc:
The timer passes itself as the argument
so your timer expect other argument type, NSTimer. Try this code:
override func viewDidLoad() {
super.viewDidLoad()
var params : [String] = []
params.append(conversion)
params.append(message)
let timer = NSTimer(fireDate: date, interval: 60, target: self, selector: #selector(sendMessage(_:)), userInfo: params, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
func sendMessage(sender: NSTimer) {
print(sender.userInfo as? [String])
}
This code works fine in my standard view controller
The API of NSTimer is such that the receiver (target function) must take one and only one parameter, of type NSTImer.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/#//apple_ref/occ/clm/NSTimer/scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
The message to send to target when the timer fires.
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
In Swift the signature must be
edit: updated to match your code exactly
Note: Swift 2.2 syntax
class TextMessageViewController: UIViewController {
var importTextMessage: AddMessageViewController // init'd somehow
override func viewDidLoad() {
super.viewDidLoad()
// ...
// Note how selector matches *the name of the class*
// As I understand it now, the sendMessage method
// is declared in the AddMessageViewController class,
// and importTextMessage is the name of the instance of that class.
// And we're in a different class now, TextMessageViewController,
// so using `self` does not make sense here.
let timer = NSTimer(fireDate: date, interval: 60, target: importTextMessage,
selector: #selector(AddMessageViewController.sendMessage(_:)), userInfo: params, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
}
}
class AddMessageViewController: UIViewController {
// func must not be private
func sendMessage(timer: NSTimer) {
// And now for example
print("params: \(timer.userInfo)")
}
}
As far as your userInfo dictionary, that is a property of the timer object, not a parameter to the target function itself.
The SECOND mistake is that the selector syntax is failing to use the name of the class as declared, not the name of some instance variable that has a reference to that class instance. Include more context in your question so it'll get solved faster!
The THIRD mistake is that you're using the wrong class name. If you use self, then the class name in the selector must match the name of the current class. Since you really want the message to go to a different class, you must use that reference instead. See updated code.
I think you still don't fully grasp object references, and their types, and how they must match, and how self is just the reference to the contextual object.

NSTimer "target": How to reference a method of a class?

I'm trying to wrap my head around Swift. Currently I don't understand how this piece of code should work:
backgroundTimer = NSTimer.scheduledTimerWithTimeInterval(3, target: GameViewController(), selector: "addNext", userInfo: nil, repeats: true)
This statement crashes the game with
MyApp.GameViewController addNext]: unrecognized selector sent to instance 0x7b1cd0b0'
Basically I have a method in GameViewController class that I would like to call from all scenes with a timer. How can I reference gameViewController.addNext()? Basically what should I put in "target" ?
Here's the GameViewController and addNext() method:
class GameViewController: GAITrackedViewController, AVAudioPlayerDelegate {
...
func addNext() {
...
}
}
When you pass GameViewController() for the target, Swift creates a temporary instance of GameViewController, and gives it to NSTimer as the target for the addNext call. This is most certainly not what you want: you need a call to be made on the instance of your view controller, not on some temporary instance.
If you make timer registration from a method of GameViewController, say, from viewDidLoad, then you can pass self for the target parameter:
override func viewDidLoad() {
super.viewDidLoad()
backgroundTimer = NSTimer.scheduledTimerWithTimeInterval(
3
, target: self
, selector: #selector(GameViewController.addNext)
, userInfo: nil
, repeats: true
)
}

How to continue a function across ViewControllers using Swift?

I have a NSTimer() that starts upon a button click. If I want the timer to be displayed on a label, I can do that within the same view controller, but I would like the timer to continue and the values (1...2...3) to be displayed on the next view controller. I have a label that displays the time variable on the next view controller but it only displays the value of the time variable when the button is pressed and does not continue. The time variable is passed but the function that runs it is not. How can I go about doing that?
var timer = NSTimer()
var time = 0
inside viewDidLoad :
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("timerFunc"), userInfo: nil, repeats: true)
func timerFunc() {
time++
//time label displays the time (1..2...3 etc)
timeLabel.text = String(time)
}
SecondSceneViewController has a time variable that is passed from this view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var secondScene = segue.destinationViewController as!
SecondViewController
secondScene.time = time
}
When I go to the secondViewController, the value inside the time variable is whatever the time variable was when the button was pressed and it does not continue running. How would I go about passing the timer to the next view controller to display the values?
IMO you should extract the code for the timer into a Singleton and then access it from both ViewControllers
Here's a simple one to get you started:
class TimerManager {
var realSharedInstance: TimerManager?
var sharedInstance: TimerManager {
get{
if let realSharedInstance = realSharedInstance {
return realSharedInstance
}
else{
realSharedInstance = TimerManager()
return realSharedInstance!
}
}
}
var timer: NSTimer
init() {
timer = NSTimer()
}
func rest() {
timer = NSTimer()
}
}
I would create a singleton that will handle the timer event.
let myTimerNotificationNameKey = "timerKey"
let myTimerNotificationUserInfoTimeKey = "timerUserInfoKey"
class myTimer {
static let sharedInstance = myTimer()
var timer: NSTimer
var time = 0
func startTimer() {
time = 0
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "timerFunc:", userInfo: nil, repeats: true)
}
func stopTimer() {
timer.invalidate()
timer = nil
}
func timerFunc(timer: NSTimer) {
NSNotificationCenter.defaultCenter().postNotificationName(myTimerNotificationNameKey,object: nil, userInfo: {myTimerNotificationUserInfoTimeKey: time++})
}
}
On the first view controller you call myTimer.sharedInstance.startTimer() function that will start the timer. But first remember to listen to the notifications. The notification will receive a user info dictionary with the time count. Do the same thing on the second view controller.
Now you have a count that you can listen across multiple view controllers. Remember to stop the timer once you stop needing it.
Note: I haven't compiled this code.
If you keep these two variables
var timer = NSTimer()
var time = 0
outside the class they become global and you can access them from another view controller.
like this example:
import UIKit
import CoreData
var fromJSON = true
// Retreive the managedObjectContext from AppDelegate
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
class CoreDataController: UIViewController {
Another option would be working with a notification center:
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "batteryLevelChanged:",
name: UIDeviceBatteryLevelDidChangeNotification,
object: nil)
#objc func batteryLevelChanged(notification: NSNotification){
//do stuff
}

Swift selector to protocol function?

I have code like this:
protocol FooP {
...
}
extension FooP {
func doFoo() {
print("foo")
}
func doFoo(timer: NSTimer) {
doFoo()
}
}
class A : NSObject, UITableViewDataSource, FooP {
var timer : NSTimer?
...
func startUpdating() {
timer = NSTimer.scheduledTimerWithTimeInterval(
1.0,
target: self,
selector: Selector("doFoo:"),
userInfo: nil,
repeats: true
)
}
}
Unfortunately it crashes when I start timer the program crashes with
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[xyz.A doFoo:]: unrecognized selector sent to instance 0x7fb2041c4ac0'
How i can make it work (I want to keep implementation of doFoo inside protocol)?
If I move doFoo into A class definition everything works fine, but as i said i want to implement this function inside protocol.
In other words I need selector that says
"Hey I point to function named "doFoo" that is implemented as extension to FooP"
Right now selector seems to say
"Hey I point to function named "doFoo" that is implemented in A class"
Try to play in your playground. Your trouble is, that there is no possibility to define #objc func in protocol extension. So, see possible workaround
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
import Foundation
protocol FooP {
}
extension FooP {
func doFoo() {
print("foo")
}
func doFoo(timer: NSTimer) {
print("dofoo")
doFoo()
}
}
class A: FooP {
var timer : NSTimer?
#objc func foo(timer: NSTimer) {
doFoo(timer)
}
func startUpdating() {
timer = NSTimer.scheduledTimerWithTimeInterval(
1.0,
target: self,
selector: "foo:",
userInfo: nil,
repeats: true
)
}
}
let a = A()
a.startUpdating()
Why it works for you if you move doFoo inside class A? That is because your class inherits from NSObject, so #objc keyword is not necessary.
The problem is, NSTimer and the whole Selector() business are Objective-C stuff and do work in Swift domain thanks to bridging. However, Swift's default protocol implementations are not bridged to Objective-C wonderland (yet), and that's why your timer fails. Basically, from Objective-C perspective objects of type A do not respond to a selector doFoo:, period.
So, report this use-case to swift-evolution for the long-term solution. Short-term, use some sort of a workaround.
BTW, you might find it interesting to read (or even participate) in this thread.

NSTimer, setNeedsDisplay, unrecognized selector issues

The logic for this app is functioning, but the timer is not functioning properly. I have three files: ViewController.Swift, clock.swift, and clockstylekit.swift.
updateTime(), located in the clock.swift file pulls the time, breaks it into its components, and returns hours, minutes and seconds and summons drawRect to redraw the visual representation of the time.
import UIKit
import Foundation
class timeUpdate: UIView {
func updateTime() -> (Int, Int, Int) {
var date:NSDate = NSDate()
var calendar:NSCalendar = NSCalendar.currentCalendar()
var components:NSDateComponents = calendar.components(NSCalendarUnit.CalendarUnitHour | NSCalendarUnit.CalendarUnitMinute | NSCalendarUnit.CalendarUnitSecond, fromDate: date)
var hour = components.hour
var minute = components.minute
var second = components.second
setNeedsDisplay()
return (hour, minute, second)
}
}
The drawRect() method also located in the clock.swift file draws out the time components to a UIView:
class clock: UIView {
override func drawRect(rect: CGRect) {
let (hour, minute, second) = timeUpdate().updateTime()
switch hour {
case 0:
ClockStyleKit.drawCanvas0()
case 01, 13:
ClockStyleKit.drawCanvas0()
ClockStyleKit.drawCanvas01()
.....etc etc
}
}
This is the timer I am using:
var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateTime"), userInfo: nil, repeats: true)
When I put this into the ViewController viewDidLoad function, I get an unrecognized selector error. When I place this into a function in my clock.swift file, I can compile without any errors, the time is updated properly, but it only runs once and does not update. Where should this timer go?
The signature for your timer function is wrong. According to the documentation the selector must have the following signature:
- (void)timerFireMethod:(NSTimer *)timer
So you need to create a timer method something like:
func updateTime(time:NSTimer) {
// your code here
}
Then call it using Selector(updateTime:)
I would suggest that you simply have the timer request the clock display to refresh and in the display code calculate the time just before you display it.
If that var timer = NSTimer... code is in your ViewController function, then self is going to be the ViewController. In your timer, you set the target to be equal to self so it's going to look for a method called updateTime in your ViewController, and it's not there because it's in the TimeUpdate class. Also, your selector's signature is wrong. You're calling updateTime(), but it wants to be calling a selector with a parameter of NSTimer that returns a void, so you need to call a routine with this signature:
func firedTimerMethod(timer: NSTimer)
Reread the description of NSTimer in the docs in Xcode.
What I had to do was place my timer in my viewcontroller like so:
class ViewController: UIViewController {
var timer = NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("firedTimerMethod:"), userInfo: nil, repeats: true)
}
#objc func firedTimerMethod(timer: NSTimer) {
println("firedTimerMethod fired")
let clockView = self.view as! clock
clockView.setNeedsDisplay()
}
}
The timer's selector function needed the NSTimer parameter, the #objc, and I had to cast the variable as the clock class. Once I did these things, drawRect now properly updates each second the timer runs. I appreciate the help from everyone, all of the various clues got me on the right track eventually.

Resources