Nstimer() - Timer.Invalidate Not Working on a Simple Stopwatch

NSTimer() - timer.invalidate not working on a simple stopwatch?

In playButton() you are defining another timer, that can't be invalidated from outside this function - so by calling timer.invalidate() you invalidate just var timer = NSTimer() which doesn't carry any set timer in it.

So replace

var timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)

with

timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)

How to invalidate an NSTimer that was started multiple times

You can add timer.invalidate() before starting a new timer in startTimerButtonTapped if you want to reset the timer each time the "start" button is tapped:

@IBAction func startTimerButtonTapped(sender: UIButton) {
timer.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}

I was going to update with an explanation but @jcaron already did it in the comment, so I'm just quoting his text, no need to change it:

Every time you tap on the "Start Timer" button, you create a new timer, while leaving the previous one running, but with no reference to it (since you've overwritten timer with the new timer you just created). You need to invalidate the previous one before you create the new one.

swift invalidate timer doesn't work

The usual way to start and stop a timer safely is

var timer : Timer?

func startTimer()
{
if timer == nil {
timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
}
}

func stopTimer()
{
timer?.invalidate()
timer = nil
}

startTimer() starts the timer only if it's nil and stopTimer() stops it only if it's not nil.

You have only to take care of stopping the timer before creating/starting a new one.

Stopwatch using NSTimer incorrectly includes paused time in display

Your calculation of the current display always uses the original start time of the timer, so the display after pausing includes the interval that the timer was paused.

The easiest thing to do would be to store another NSTimeInterval, say secondsAlreadyRun, when the timer is paused, and add that to the time interval you calculate when you resume. You'll want to update the timer's startDate every time the timer starts counting. In reset:, you would also clear out that secondsAlreadyRun interval.

-(void)showActivity:(NSTimer *)tim {

NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
// Add the saved interval
timeInterval += secondsAlreadyRun;
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
stopWatchLabel.text = timeString;
[dateFormatter release];
}

- (IBAction)onStartPressed:(id)sender {

stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1/10
target:self
selector:@selector(showActivity:)
userInfo:nil
repeats:YES];
// Save the new start date every time
startDate = [[NSDate alloc] init]; // equivalent to [[NSDate date] retain];
[stopWatchTimer fire];
}

- (IBAction)onStopPressed:(id)sender {
// _Increment_ secondsAlreadyRun to allow for multiple pauses and restarts
secondsAlreadyRun += [[NSDate date] timeIntervalSinceDate:startDate];
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[startDate release];
[self showActivity];
}

- (IBAction)reset:(id)sender; {
secondsAlreadyRun = 0;
stopWatchLabel.text = @"00:00.00";
}

Don't forget to release that startDate somewhere appropriate! Also keep in mind that the documented NSTimer interface is for the method you give it to accept one argument, which will be the timer itself. It seems to work without that, but why tempt fate?

Finally, since you're using that NSDateFormatter so much, you might want to consider making it an ivar or put it in static storage in showActivity:, like so:

static NSDateFormatter * dateFormatter = nil;
if( !dateFormatter ){
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"mm:ss.SS"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
}

Accuracy of NSTimer

Here's a class you can use to do what you want:

@interface StopWatch()
@property ( nonatomic, strong ) NSTimer * displayTimer ;
@property ( nonatomic ) CFAbsoluteTime startTime ;
@end

@implementation StopWatch

-(void)dealloc
{
[ self.displayTimer invalidate ] ;
}

-(void)startTimer
{
self.startTime = CFAbsoluteTimeGetCurrent() ;
self.displayTimer = [ NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector( timerFired: ) userInfo:nil repeats:YES ] ;
}

-(void)stopTimer
{
[ self.displayTimer invalidate ] ;
self.displayTimer = nil ;

CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
[ self updateDisplay:elapsedTime ] ;
}

-(void)timerFired:(NSTimer*)timer
{
CFAbsoluteTime elapsedTime = CFAbsoluteTimeGetCurrent() - self.startTime ;
[ self updateDisplay:elapsedTime ] ;
}

-(void)updateDisplay:(CFAbsoluteTime)elapsedTime
{
// update your label here
}

@end

The key points are:

  1. do your timing by saving the system time when the stop watch is started into a variable.
  2. when the the stop watch is stopped, calculate the elapsed time by subtracting the stop watch start time from the current time
  3. update your display using your timer. It doesn't matter if your timer is accurate or not for this. If you are trying to guarantee display updates at least every 0.1s, you can try setting your timer interval to 1/2 the minimum update time (0.05s).

How can I use Timer (formerly NSTimer) in Swift?

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.

Swift how to prevent multiple NSTimer.scheduledTimerWithTimeInterval

You need to declare a variable on the top of your class:

 var timer = NSTimer()

then you assign this timer variable in your start button, like so:

@IBAction func startTimerButtonTapped(sender: UIButton) {
time = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)
}

whenever you want to stop this timer, you need to invalidate it, like so:

timer.invalidate()

If you want to click the start button two more time, you need to invalidate it first before schedule it:

@IBAction func startTimerButtonTapped(sender: UIButton) {
timer.invalidate()
time = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("result"), userInfo: nil, repeats: true)
}

Hope this will help you.

Stop watch working fine only first time in Swift

Your pause action should be:

@IBAction func pause(sender: AnyObject) {

timer.invalidate()
pause = true

}

UPD:

var time = 0
var timer = NSTimer()
var pause = true // 1

func displayResult() {

if pause != true
{
time++
label.text = String(time)
}
}
@IBAction func reset(sender: AnyObject) {

time = 0

label.text = String(time)
timer.invalidate() // 2
pause = true // 3
}

@IBAction func pause(sender: AnyObject) {
timer.invalidate() // 4
pause = true

}

@IBAction func play(sender: AnyObject) {

// 5
if pause == true {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("displayResult") , userInfo: nil, repeats: true)
pause = false
}
}

timer.invalidate() causes a crash,saying thread1:breakpoint1.1

You may have placed a breakpoint accidently.

In your code on the line:

count = 1

Now on that line look left and you will see a blue arrow.

You can delete the arrow by dragging this arrow to the right and let go.

Hope this helped you!

How do I use NSTimer?

Firstly I'd like to draw your attention to the Cocoa/CF documentation (which is always a great first port of call). The Apple docs have a section at the top of each reference article called "Companion Guides", which lists guides for the topic being documented (if any exist). For example, with NSTimer, the documentation lists two companion guides:

  • Timer Programming Topics for Cocoa
  • Threading Programming Guide

For your situation, the Timer Programming Topics article is likely to be the most useful, whilst threading topics are related but not the most directly related to the class being documented. If you take a look at the Timer Programming Topics article, it's divided into two parts:

  • Timers
  • Using Timers

For articles that take this format, there is often an overview of the class and what it's used for, and then some sample code on how to use it, in this case in the "Using Timers" section. There are sections on "Creating and Scheduling a Timer", "Stopping a Timer" and "Memory Management". From the article, creating a scheduled, non-repeating timer can be done something like this:

[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(targetMethod:)
userInfo:nil
repeats:NO];

This will create a timer that is fired after 2.0 seconds and calls targetMethod: on self with one argument, which is a pointer to the NSTimer instance.

If you then want to look in more detail at the method you can refer back to the docs for more information, but there is explanation around the code too.

If you want to stop a timer that is one which repeats, (or stop a non-repeating timer before it fires) then you need to keep a pointer to the NSTimer instance that was created; often this will need to be an instance variable so that you can refer to it in another method. You can then call invalidate on the NSTimer instance:

[myTimer invalidate];
myTimer = nil;

It's also good practice to nil out the instance variable (for example if your method that invalidates the timer is called more than once and the instance variable hasn't been set to nil and the NSTimer instance has been deallocated, it will throw an exception).

Note also the point on Memory Management at the bottom of the article:

Because the run loop maintains the timer, from the perspective of memory management there's typically no need to keep a reference to a timer after you’ve scheduled it. Since the timer is passed as an argument when you specify its method as a selector, you can invalidate a repeating timer when appropriate within that method. In many situations, however, you also want the option of invalidating the timer—perhaps even before it starts. In this case, you do need to keep a reference to the timer, so that you can send it an invalidate message whenever appropriate. If you create an unscheduled timer (see “Unscheduled Timers”), then you must maintain a strong reference to the timer (in a reference-counted environment, you retain it) so that it is not deallocated before you use it.



Related Topics



Leave a reply



Submit