NSTimer, setNeedsDisplay, unrecognized selector issues - ios

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.

Related

Invalidating NSTimer

I have 4 NSTimers objects in my app , that make requests to a rest URL every few seconds.
On clicking of a particular button I want to stop the timer so that it stops polling and on click of another button I want to resume polling.
I have tried invalidate for all timers but does not work.
NOTE: All timers are in different class and I'm trying to invalidate the timers in another class
Any help will be appreciated , thank you.
class NotificationViewController:UIViewController {
var timer:NSTimer?
getEvents(){
if((timer == nil)) {
timer = NSTimer.scheduledTimerWithTimeInterval(20.0, target: self, selector: Selector("getEvents"), userInfo: nil, repeats: true)
}
}
}
In another class I'm doing this on click of a button
class Menu:UIViewController {
#IBAction func buttonTapped(sender: UIButton) {
self.notify.timer?.invalidate()
self.notify.timer = nil
}
}
It seems that you are losing the original reference to the timers. One possible evil solution, is to keep the reference across the app. You can do that using struct
:
struct Timers {
static var firstTimer = NSTimer()
static var secondTimer = NSTimer()
static var thirdTimer = NSTimer()
static var fourthTimer = NSTimer()
}
This way you could access the timer from anywhere in the program:
Timers.firstTimer.invalidate()
Try to create a separate NotificationTimer class and used its shared object all over the project like this way.
class NotificationTimer: NSObject {
var timer1: NSTimer?
var timer2: NSTimer?
var timer3: NSTimer?
var timer4: NSTimer?
static let sharedManager = NotificationTimer()
func getEvents(){
print("Fired")
if (timer1 == nil){
timer1 = NSTimer.scheduledTimerWithTimeInterval(20.0, target: self, selector: Selector("methodYouWantToCall"), userInfo: nil, repeats: true)
}
}
}
Now call this timer1 object inside any ViewController like this way.
let notificationTimer = NotificationTimer.sharedManager
notificationTimer.timer1?.invalidate()
Or call that method like this way
NotificationTimer.sharedManager().getEvents()
Cancelling the timer:
if (timer.isValid()) {
timer.invalidate()
}
timer = nil;
Just debug you app and check where you are not releasing the timer.
Bear in mind, that timer retains it's target, so if you want to release the target, you will need to release the timer as well, or you can write your own timer with week reference to the target.

NSTimer not firing propery

I have set up a timer class to firer when the viewDidLoad(). I want to have a timer on multiple view controllers thoughout the app. If you have a better solution to a accurate timer on multiple views please suggest.
Viewcontroller -> One of the views that needs a timer
override func viewDidLoad() {
super.viewDidLoad()
func setupTimer() {
// Setupt the timer, this will call the timerFired method every second
var timer = NSTimer(
timeInterval: 1.0,
target: self,
selector: #selector(TestTimer.timerFired()),//<- Error Code
userInfo: nil,
repeats: true)
}
The Error Code: Use of instance member 'timerFired' on type 'TestTimer',did you mean to use a value of type 'TestTimer' instead?
Timer Class -> Checks start date compared to current date/time for a accurate timer
class TestTimer: NSTimer {
var timer = NSTimer()
// Converter changes String into NSDate
var startDate = converter("Tue, 26 Apr 2016 09:01:00 MDT")
// Function to be fired
func timerFired() {
let now = NSDate()
let difference = now.timeIntervalSinceDate(self.startDate)
// Format the difference for display
// For example, minutes & seconds
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.stringFromTimeInterval(difference)
print(difference)
}
}
The error you're getting is pretty obscure. What it's trying to tell you is you should remove the () from the end of your timerFired in the #selector.
var timer = NSTimer(
timeInterval: 1.0,
target: self,
selector: #selector(TestTimer.timerFired),
userInfo: nil,
repeats: true)
However, this isn't going to make your code how you want it to work – as self in the timer declaration refers to the view controller, not the timer. I would recommend you create a wrapper class for NSTimer, along with a delegate pattern in order to achieve what you want.
You should note that the documentation states that you shouldn't attempt to subclass NSTimer, so you could do something like this instead:
// the protocol that defines the timerDidFire callback method
protocol TimerDelegate:class {
func timerDidFire(cumulativeTime:NSTimeInterval)
}
// your timer wrapper class
class TimerWrapper {
// the underlying timer object
weak private var _timer:NSTimer?
// the start date of when the timer first started
private var _startDate = NSDate()
// the delegate used to implement the timerDidFire callback method
weak var delegate:TimerDelegate?
// start the timer with a given firing interval – which could be a property
func startTimer(interval:NSTimeInterval) {
// if timer already exists, make sure to stop it before starting another one
if _timer != nil {
stopTimer()
}
// reset start date and start new timer
_startDate = NSDate()
_timer = NSTimer.scheduledTimerWithTimeInterval(interval,
target: self,
selector: #selector(timerDidFire),
userInfo: nil, repeats: true)
}
// invalidate & deallocate the timer,
// make sure to call this when you're done with the timer
func stopTimer() {
_timer?.invalidate()
_timer = nil
}
// make sure to stop the timer when the wrapper gets deallocated
deinit {
stopTimer()
}
// called when the timer fires
#objc func timerDidFire() {
// get the change in time, from when the timer first fired to now
let deltaTime = NSDate().timeIntervalSinceDate(_startDate)
// do something with delta time
// invoke the callback method
delegate?.timerDidFire(deltaTime)
}
}
You can then use it like this:
// your view controller class – make sure it conforms to the TimerDelegate
class ViewController: UIViewController, TimerDelegate {
// an instance of the timer wrapper class
let timer = TimerWrapper()
override func viewDidLoad() {
super.viewDidLoad()
// set the timer delegate and start the timer – delegate should be set in viewDidLoad,
// timer can be started whenever you need it to be started.
timer.delegate = self
timer.startTimer(1)
}
func timerDidFire(cumulativeTime: NSTimeInterval) {
// do something with the total time
let dateComponentsFormatter = NSDateComponentsFormatter()
let text = dateComponentsFormatter.stringFromTimeInterval(cumulativeTime)
label.text = text
}
}
As far as the appropriateness of using an NSTimer here goes, as you're only using a time interval of 1 second, an NSTimer is suitable. By taking the time interval over the total timer duration, you can average out any small firing inaccuracies.
This is how a timer is initialized
var timer = NSTimer.scheduledTimerWithTimeInterval(1.0 , target: self, selector: #selector(TestTimer.timerFired()), userInfo: nil, repeats: true)

Selector is not fired from scheduledTimerWithTimeInterval

I checked the existing posts on this topic and also googled it, but I am not able to identify my mistake or make this work for me. I have a function iterativeDeepening() inside the class ChessPlayer. After say 15 seconds I want to stop further iterations within the function. In the code below, the function "flagSetter" is never invoked. If I use NSTimer.fire() the function is invoked immediately and not after 15 seconds. I tried placing the flagSetter function before or after iterativeDeepening(). Either case does not work. What have I done incorrectly?
class ChessPlayer {
var timeoutFlag = false
//Code
func iterativeDeepening() {
***//variables and constants***
let timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target: self, selector: #selector(self.flagSetter), userInfo: nil, repeats: false)
***while minDepth <= maxDepth
{
// Loop iteration code
if timeoutFlag { break out of loop }
}***
}
#objc func flagSetter(timer: NSTimer) {
print("flag changed to true")
self.timeoutFlag = true
timer.invalidate()
}
}
The requirement:
computerThinking() is fired from GameScene from human move's action completion handler.
GameScene.computerThinking() invokes ChessPlayer.iterativeDeepening()
iterativeDeepening runs a while loop incrementing "depth". For each "depth" an optimal move at that depth is evaluated. Higher the depth, more detailed the evaluation.
after 15.0 seconds i want to break out of the while loop with the depth and optimal move available at that point of time.
I am a lover of Objective-c and never used Swift yet in my projects. Googling NSTimer Swift, I found out the following steps to implement NSTimer correctly.
we need to define our NSTimer. The first variable that we are going to need is a variable called timer of type NSTimer. We do this like:
var timer = NSTimer()
Start NSTimer timer:
timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target:self, selector:#selector(ChessPlayer.flagSetter(_:)), userInfo: nil, repeats: false)
And your method flagSetter should be defined as this:
func flagSetter(timer: NSTimer) {
print("flag changed to true")
self.timeoutFlag = true
timer.invalidate()
}
This will now surely work as I've made my very first app, just for this question, made in Swift. Check how I put my selector. You're right with the warnings by the way.
If you need more information about Selectors, check this thread: #selector() in Swift?
Here is your solution: define timer outside of the function so you can invalidate it from another function. Right now, your timer is defined inside a function so it can only be altered inside that function, but that is not what you want. Fix it by doing the following: right below var timeoutFlag = false put var timer = NSTimer(). Then inside your function iterativeDeepening() get rid of the let. Then it will all work!!
Here's what your code should be, adapted from Hasya's answer and your provided code.
class ChessPlayer {
// Declare timer and timeoutFlag
var timer = NSTimer()
var timeoutFlag = false
func iterativeDeepening() {
self.timer = NSTimer.scheduledTimerWithTimeInterval(15.0, target: self, selector: “timerEventOccured”, userInfo: nil, repeats: true)
}
func timerEventOccured() {
print("timerEventOccured was called.")
timeoutFlag = true
self.timer.invalidate()
}
}
override func viewDidUnload() {
super.viewDidUnload()
self.timer.invalidate()
}
}

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
}

NSTimer questions (closure, #objc, and etc.)

I'm pretty new to swift and I have some questions about swift and even just basic OOP (so if you can, please be specific with your answers, thanks a lot!)
So I am making an app that has a timer component and the follow code snippets are from that timer class (and the view controller):
I have stop/start function and a deinit:
var timer = NSTimer()
...
func start(){
self.timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "tick", userInfo: nil, repeats: true)
}
func stop(){
timer.invalidate()
}
....
deinit(){
self.timer.invalidate()
}
so my first question is, why do I need to call self.timer in deinit and start but not in stop? Also when does deinit get called and what does it do differently than the stop function (seems to me they both invalidate the NSTimer)?
There is also an initializer:
init (duration: Int, handler: (Int) -> ()){
self.duration = duration
self.handler = handler
}
and the initializer is called in the view controller:
private func timerSetUP(hr: Bool, duration: Int){
timer = Timer(duration: duration){
(elapsedTime: Int) -> () in
let timeRemaining = duration - elapsedTime
println("what is the eT: \(elapsedTime)")
let timeReStr = self.getHrMinSecLabels(hr, timeremaining: timeRemaining)
....}
My question is about the closure, elapsedTime is a property in Timer class and is it just getting passed into the closure in the view controller? Why is there no reference to the Timer class (like timer.elapsedTime)? And I don't really need this closure right? I can just have another function that does the same thing (or is this easier to get the elapsedTime using this closure)?
There is also a Tick function in my Timer class:
#objc func tick(){
self.elapsedTime++
self.handler(elapsedTime)
if self.elapsedTime == self.duration{
self.stop()
}
}
This is the selector for self.timer = NSTimer.scheduledTimerWithTimeInterval(), is a selector just a function that gets called every time the timer fires? And do I just need to give NSTimer.scheduledTimerWithTimeInterval() the string name of the selector function? Also why is there #objc, this just looks like a swift function to me?
Your questions are loaded. Let me try addressing them one at a time:
why do I need to call self.timer in deinit and start but not in stop
It's personal coding style. self.timer is the same as timer, assuming you don't have a local variable overriding the instance variable.
Also when does deinit get called and what does it do differently than the stop function?
deinit is called when the run time deallocates your object. If you timer is still running at that time, it needs to stop it first. stop just stops the timer but keep the object in memory.
My question is about the closure, elapsedTime is a property in Timer class...
You need to understand closure a bit. elapsedTime is a parameter of the closure/anonymous function. The Timer object passes its elapsedTime property to that anonymous function when the timer fires. Work the same if you rename it like this:
timer = Timer(duration: duration){
(t : Int) -> () in
let timeRemaining = duration - t
println("what is the eT: \(t)")
let timeReStr = self.getHrMinSecLabels(hr, timeremaining: timeRemaining)
....}
is a selector just a function that gets called every time the timer fires?
Yes. But you need to specify what object to call the function on (see the target parameter in the next question).
And do I just need to give NSTimer.scheduledTimerWithTimeInterval() the string name of the selector function?
Yes, something like this:
// Fire the tellTime() function of the current object every second (1000ms)
self.timer = NSTimer.scheduledTimerWithTimeInterval(
timeInterval: 1000,
target: self,
selector: "tellTime",
userInfo: nil,
repeats: true)
Also why is there #objc, this just looks like a swift function to me?
This is to make Objective-C code aware of your Swift function. You can skip it if you program exclusively in Swift.

Resources