NSTimer keeps counting even when the timer visually has stopped - ios

I'm working on creating a timer, which is in the format of HH:mm, but my issue right now is pausing the timer and resuming it. Right now, when you pause the timer, it continues to count in the background, but the visible portion has stopped. Once you click on the resume button, the timer updates to where it left off, which, pending how long you it had been paused, could be any number above what it should be.
For instance, if the timer counted to 5, you then pause the timer, which now is sitting at 5, once you click resume, the timer may start at 15 since it had been 10 seconds since you paused it. Below is my code, which I have combined from several places to get this working:
- (void) createTimerPage
{
_dateFormat = [NSDate date];
// Add pause button
UIImage *pause = [UIImage imageNamed:#"pause.png"];
_pauseBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_pauseBtn.frame = CGRectMake(151, 17, 32, 32);
[_pauseBtn addTarget:self action:#selector(pauseTimer) forControlEvents:UIControlEventTouchUpInside];
[_pauseBtn setBackgroundImage:pause forState:UIControlStateNormal];
}
- (void) startTimer
{
// Start timer
NSInteger secondsSinceStart = (NSInteger) [[NSDate date] timeIntervalSinceDate:_dateFormat];
NSInteger seconds = secondsSinceStart % 60;
NSInteger minutes = (secondsSinceStart / 60) % 60;
NSString *result = nil;
result = [NSString stringWithFormat:#"%02d:%02d", minutes, seconds];
_timerText.text = result;
[_timerBtns addSubview:_pauseBtn];
}
- (void) startPressed
{
[_time invalidate];
_time = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(startTimer)
userInfo:nil
repeats:YES];
}
- (void) pauseTimer
{
if (_time) {
// pause timer
[_time invalidate];
[_timerBtns addSubview:_playBtn];
}
}

I suspect the problem is that you are storing the "start time" in the variable called "_dateFormat". That variable only gets set when createTimerPage is called so, of course, when you later attempt to determine the secondsSinceStart in startTimer, it is still computing the time since createTimerPage was called.
Honestly, for what you are trying to do, I would not worry about a start time at all. Instead, I would just use a counter of the number of times the timer method gets called. I would also rename some of your methods to make it clearer what is actually going on. For example, your "startTimer" method isn't actually starting a timer. It's what gets called when your one second timer fires.
Finally, one other issue is that you are constantly adding subviews but, at least with the code posted, never removing them. You could (should) remove the unused subview every time you add a new one. Honestly, though, you would be better off leaving both views as part of your view hierarchy and using the hidden attribute to only show one of them.
Something like this will do what you want:
NSUInteger _timerSeconds;
NSTimer * _timer;
- (void) createTimerPage
{
// Add pause button
UIImage *pause = [UIImage imageNamed:#"pause.png"];
_pauseBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_pauseBtn.frame = CGRectMake(151, 17, 32, 32);
[_pauseBtn addTarget:self action:#selector(pausePressed) forControlEvents:UIControlEventTouchUpInside];
[_pauseBtn setBackgroundImage:pause forState:UIControlStateNormal];
[_timerBtns addSubview:_pauseBtn];
}
- (void) timerFired
{
_timerSeconds++;
NSInteger seconds = _timerSeconds % 60;
NSInteger minutes = (_timerSeconds / 60) % 60;
NSString * result = [NSString stringWithFormat:#"%02d:%02d", minutes, seconds];
_timerText.text = result;
}
- (void) startPressed
{
// start timer
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timerFired)
userInfo:nil
repeats:YES];
_playBtn.hidden = YES;
_pauseBtn.hidden = NO;
}
- (void) pausePressed
{
if (_timer) {
// pause timer
[_timer invalidate];
_playBtn.hidden = NO;
_pauseBtn.hidden = YES;
}
}

I was actually able to figure this one out with some more research on the matter. I found a great example that helped me come up with the following:
- (IBAction)startRepeatingTimer:(id)sender
{
// Show feed text while timer is going
_feeding = [[UILabel alloc] initWithFrame:CGRectMake(92, 25, 100, 50)];
_feeding.text = #"FEEDING";
_feeding.textColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:1];
if (_feeding) {
_feeding.hidden = NO;
}
[_timerArea addSubview:_feeding];
// Cancel preexisting timer
[_repeatingTimer invalidate];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timerTicked:)
userInfo:nil repeats:YES];
_repeatingTimer = timer;
if (_playBtn.hidden == NO) {
_playBtn.hidden = YES;
_pauseBtn.hidden = NO;
}
if (_bWindow.frame.origin.y == 0) {
[UIView animateWithDuration:0.3
animations:^ {
_bWindow.frame = CGRectMake(0, 0, self.view.frame.size.width, 203);
_bWindow.frame = CGRectMake(0, -130, self.view.frame.size.width, 203);
}];
}
[_timerBtns addSubview:_pauseBtn];
}
- (void) timerTicked:(NSTimer *)timer
{
_currentTimeInSeconds++;
_timerText.text = [self targetMethod:_currentTimeInSeconds];
}
- (IBAction)resetTimer:(id)sender
{
_feeding.hidden = YES;
if (_repeatingTimer) {
[_repeatingTimer invalidate];
}
if (_playBtn.hidden == YES) {
_playBtn.hidden = NO;
_pauseBtn.hidden = YES;
}
_currentTimeInSeconds = 0;
_timerText.text = [self targetMethod:_currentTimeInSeconds];
if (_bothBtn.hidden == YES) {
_bothBtn.hidden = NO;
_switchBView.hidden = YES;
}
if (_rightBtn.hidden == YES) {
_rightBtn.hidden = NO;
_switchRView.hidden = YES;
}
if (_leftBtn.hidden == YES) {
_leftBtn.hidden = NO;
_switchLView.hidden = YES;
}
}
- (IBAction)pauseRepeatingTimer:(id)sender
{
[_repeatingTimer invalidate];
if (_playBtn.hidden == YES) {
_playBtn.hidden = NO;
_pauseBtn.hidden = YES;
}
}
- (NSString *) targetMethod:(int)totalSeconds
{
//NSInteger secondsSinceStart = (NSInteger) [[NSDate date] timeIntervalSinceDate:_dateFormat];
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
return [NSString stringWithFormat:#"%02d:%02d", minutes, seconds];
}

Related

How do I restart a NSTimer?

Thank you in advance.
I am developing a game that runs on a timer. I have a label on the view *countdown. I am using a NSTimer *countdownTimer to run 120 seconds (2:00). That is when the game is over. Also, at the same time...I am running a transition timer that modals to another view controller.
I have it set up where the next view the player can select "Play Again". I have it set up where it goes back to the original view. However, how do I reset the *countdownTimer to run again from 2:00?
- (IBAction)playAgain:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
[self setTimer];
[self GameOver];
[self Score];
[self timerRun];
countdown.text = [NSString stringWithFormat:#"2:00"];
}
This is the button to play again. The countdown text does not reset. Here is my NSTimer for *countdownTimer
-(void) timerRun {
secondsCount = secondsCount - 1;
int minuts = secondsCount / 60;
int seconds = secondsCount - (minuts * 60);
NSString *timerOutput = [NSString stringWithFormat: #"%2d:%.2d", minuts, seconds];
countdown.text = timerOutput;
if (secondsCount == 0) {
[countdownTimer invalidate];
countdownTimer = nil;
}
}
-(void) setTimer {
secondsCount= 120;
countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
When it resets, it just stays at zero instead of going back to 2:00
Thank you for your help.

progress bar showing tick animation instead of smooth animation when used with NSTimer

I am using NSTimer and circular progress bar for the countdown timer i.e 15seconds
Its working with the following code but I am getting tick animation for the progress bar and not the smooth one, how to make it smooth animation
- (void)viewDidLoad
{
[super viewDidLoad];
self.labeledLargeProgressView.roundedCorners = NO;
self.labeledLargeProgressView.trackTintColor =[UIColor colorWithRed:0.0f/255.0f green:173.0f/255.0f blue:255.0f/255.0f alpha:1.0f];
self.labeledLargeProgressView.progressTintColor =[UIColor colorWithRed:255.0f/255.0f green:96.0f/255.0f blue:88.0f/255.0f alpha:1.0f];
self.labeledLargeProgressView.thicknessRatio = 1.0f;
self.labeledLargeProgressView.clockwiseProgress = YES;
[self.view addSubview:self.labeledLargeProgressView];
seconds = 15.0;
[self startAnimation];
}
- (void)progressChange
{
CGFloat progress ;
DALabeledCircularProgressView *labeledProgressView = self.labeledLargeProgressView;
if(labeledProgressView.progress >=1.0f && [self.timer isValid]){
[self stopAnimation];
seconds = 15.0f;
}
else{
progress=labeledProgressView.progress + 0.06666667f;
[labeledProgressView setProgress:progress animated:YES];
seconds --;
labeledProgressView.progressLabel.text = [NSString stringWithFormat:#"%i", seconds];
}
}
- (void)startAnimation
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(progressChange)
userInfo:nil
repeats:YES];
self.continuousSwitch.on = YES;
}
- (void)stopAnimation
{
[self.timer invalidate];
self.timer = nil;
self.continuousSwitch.on = NO;
}
What i found out is a set of both UIview animations and progress view animatons that work much nicer and animate the progressview smoothly. If you just use the combination of animation and progressview animation it is fired directly without regard to the UIview animation timer.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
timer = [NSTimer scheduledTimerWithTimeInterval: 1.0f
target: self
selector: #selector(updateTimer)
userInfo: nil
repeats: YES];
}
- (void)updateTimer
{
if (progressView.progress >= 1.0) {
[timer invalidate];
}
[UIView animateWithDuration:1 animations:^{
float newProgress = [self.progressView progress] + 0.125;
[self.progressView setProgress:newProgress animated:YES];
}];
}
Feel free to adjust the animation times to even better and smooth transitions
I solved it by myself what I did is I am updating more frequently 0.1f instead of 1.0f
- (void)progressChange
{
CGFloat progress ;
DALabeledCircularProgressView *labeledProgressView = self.labeledLargeProgressView;
if(labeledProgressView.progress >=1.0f && [self.timer isValid]){
[self stopAnimation];
seconds = 15.0f;
_counter = 0;
}
else{
progress=labeledProgressView.progress + 0.00666667f;
_counter ++;
[labeledProgressView setProgress:progress animated:YES];
if(_counter % 10 == 0){
seconds --;
}
labeledProgressView.progressLabel.text = [NSString stringWithFormat:#"%i", seconds];
}
}
- (void)startAnimation
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(progressChange)
userInfo:nil
repeats:YES];
self.continuousSwitch.on = YES;
}

NSTime update when button selected

- (void)viewDidLoad{
[super viewDidLoad];
counterLabel.text = [NSString stringWithFormat:#"Your counter is: %d",seconds];
seconds = 0;
randomTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0/1.0) target:self selector:#selector(randomMainVoid) userInfo:nil repeats:YES];
}
-(void)randomMainVoid{
seconds += 1;
counterLabel.text = [NSString stringWithFormat:#"Your counter is: %d",seconds];
}
-(IBAction)Green:(id)sender{
seconds+=2;
colorLabel.textColor = [UIColor greenColor];
colorLabel.text = #"Your color is: Green";
}
My first method is updating the seconds and displaying it. I have a button, when I click it i want the seconds to go up double. How would I do that?

How to Animate uilabel text between 3 values

What I want is to animate my UILabel's text between 3 values repeatedly. The text I'd like to set is 'downloading.', 'downloading..', 'downloading...', and then, repeat. With this animation, I want to let my user know that the downloading is being done and the app is not stacked. I've been searching with google for a while and did not find a solution.
Any one can give me some reference about this? Thanks in advance.
Use the NStimer for changing the text
in .h file
#interface ViewController : UIViewController
{
NSTimer * timer;
UILabel * downloadingLbl;
}
and in .m file
- (void)viewDidLoad
{
[super viewDidLoad];
downloadingLbl.text = #"Downloading.";
if (!timer) {
timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:#selector(onTick:) userInfo:nil repeats:YES];
}
}
-(void)onTick:(NSTimer*)timer
{
NSLog(#"Tick...");
if ([downloadingLbl.text isEqualToString:#"Downloading."]) {
downloadingLbl.text = #"Downloading..";
} else if ([downloadingLbl.text isEqualToString:#"Downloading.."]) {
downloadingLbl.text = #"Downloading...";
} else if ([downloadingLbl.text isEqualToString:#"Downloading..."]) {
downloadingLbl.text = #"Downloading.";
}
}
And when your download complete that make timer invalidate.
[timer invalidate];
for that you have to schedule the timer like this:
int count = 1;
NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(updateLabel)
userInfo:nil
repeats:YES];
Now below method will get called in every (in seconds) periods
- (void) updateLabel {
if(count == 1)
{
label.text = #"Downloading."
count = 2;
}
else if(count == 2)
{
label.text = #"Downloading.."
count = 3;
}
else if(count == 3)
{
label.text = #"Downloading..."
count = 1;
}
}
whenever you want to stop Updating do this,in scenario you want to stop downloding :
[timer invalidate];
Updated Johnston 's answer for Swift:
ellipsisTimer = NSTimer.scheduledTimerWithTimeInterval(0.6, target: self, selector: #selector(LinkCheckViewController.updateLabelEllipsis(_:)), userInfo: nil, repeats: true)
func updateLabelEllipsis(timer: NSTimer) {
let messageText: String = self.ThreeDotLabel.text!
let dotCount: Int = (ThreeDotLabel.text?.characters.count)! - messageText.stringByReplacingOccurrencesOfString(".", withString: "").characters.count + 1
self.ThreeDotLabel.text = " "
var addOn: String = "."
if dotCount < 4 {
addOn = "".stringByPaddingToLength(dotCount, withString: ".", startingAtIndex: 0)
}
self.ThreeDotLabel.text = self.ThreeDotLabel.text!.stringByAppendingString(addOn)
}
In viewDidLoad:
self.downloadingLabel.text = #"downloading.";
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(tick:) userInfo:nil repeats:YES];
Then add the method:
- (void) tick:(NSTimer *) timer {
if ([self.downloadingLabel.text isEqual: #"downloading..."])
self.downloadingLabel.text = #"downloading.";
else
self.downloadingLabel.text = [self.downloadingLabel.text stringByAppendingString:#"."];
}
This will do what you asked for. Although you may want to look into classes like UIActivityIndicatorView;
Use this. It may help you..
if (!timer)
{
timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:#selector(updateLabel) userInfo:nil repeats:YES];
}
Here I add shake animation to indicate downloading progress.
Initially the shake values are..
self.shake = 5;
self.direction = 1;
-(void)shake:(UILabel *)theOneYouWannaShake{
[UIView animateWithDuration:0.01 animations:^
{
theOneYouWannaShake.transform = CGAffineTransformMakeTranslation(5*self.directions, 0);
}
completion:^(BOOL finished)
{
if(self.shakes >= 5)
{
theOneYouWannaShake.transform = CGAffineTransformIdentity;
return;
}
self.shakes++;
self.directions = self.directions * -1;
[self shake:theOneYouWannaShake];
}];
}
- (void) updateLabel {
int status = self.count % 3;
if(status == 0)
{
label.text = #"Downloading."
}
else if(status == 1)
{
label.text = #"Downloading.."
}
else
{
label.text = #"Downloading...";
}
self.count += 1 ;
}
After download complete.
[timer invalidate]
self.count = 0;
I just came to this post in 2015 and I wasn't satisfied with the answers here.
Here's my solution. Change the 4 for as many dots as you want. This will do 3 dots (4 - 1). Change the word Posting to whatever status you need.
First add a timer as stated above.
self.ellipsisTimer = [NSTimer scheduledTimerWithTimeInterval:0.6
target:self
selector:#selector(updateLabelEllipsis:)
userInfo:nil
repeats:YES];
In your update method:
- (void) updateLabelEllipsis:(NSTimer *)timer {
NSString *messageText = [[self messageLabel] text];
NSInteger dotCount = messageText.length - [[messageText stringByReplacingOccurrencesOfString:#"." withString:#""] length] + 1;
[[self messageLabel] setText:#"Posting"];
NSString *addOn = #".";
if (dotCount < 4) {
addOn = [#"" stringByPaddingToLength:dotCount withString: #"." startingAtIndex:0];
}
[[self messageLabel] setText:[[[self messageLabel] text] stringByAppendingString:addOn]];
}
It works by counting the current number of dots and adding one more. Once it goes above the limit it goes back to 1 dot.
Change the UILabel text animation of 0.5 seconds duration with adding 3 texts in array and updating all the time until your response succeed.
User NSTimer for updating UILabel text

Stopwatch glitches

I made a stopwatch application recently and it had a few glitches.
If I hit the stop button twice in a row, the entire app would crash.
If I hit the start button twice in a row, the timer would run twice as fast and the stop button would stop working.
How do I fix this problem?
Here is the code in my .h file:
IBOutlet UILabel *time;
IBOutlet UILabel *time1;
IBOutlet UILabel *time2;
NSTimer *myTicker;
NSTimer *myTicker2;
NSTimer *myTicker3;
}
- (IBAction)start;
- (IBAction)stop;
- (IBAction)reset;
- (void)showActivity;
- (void)showActivity1;
- (void)showActivity2;
#end
and here is my code in the .m file:
- (IBAction)start {
myTicker = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
myTicker2 = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:#selector(showActivity1) userInfo:nil repeats:YES];
myTicker3 = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:#selector(showActivity2) userInfo:nil repeats:YES];
}
- (IBAction)stop {
[myTicker invalidate];
[myTicker2 invalidate];
[myTicker3 invalidate];
}
- (IBAction)reset {
time.text = #"00";
time1.text = #"00";
time2.text = #"00";
}
- (void)showActivity {
int currentTime = [time.text intValue];
int newTime = currentTime + 1;
if (newTime == 60) {
newTime = 0;
}
time.text = [NSString stringWithFormat:#"%d", newTime];
}
- (void)showActivity1 {
int currentTime1 = [time1.text intValue];
int newTime1 = currentTime1 + 1;
if (newTime1 == 10) {
newTime1 = 0;
}
time1.text = [NSString stringWithFormat:#"%d", newTime1];
}
- (void)showActivity2 {
int currentTime2 = [time2.text intValue];
int newTime2 = currentTime2 + 1;
time2.text = [NSString stringWithFormat:#"%d", newTime2];
}
Set the stop button's userInterActionEnabled property to NO and the start button's to YES when the -stop method is fired. Then switch and set the stop button's userInterActionEnabled to YES and the start button's to NO when -start is fired.
You should create a private BOOL variable "isRunning", which is checked when clicked on Stop or Start like:
- (IBAction)stop {
if(!isRunning) return;
[myTicker invalidate];
[myTicker2 invalidate];
[myTicker3 invalidate];
self.isRunning = NO;
}
etc. Also ignoring user interactions is general a good idea (like CodaFi suggested), but only fights the symptoms ;-) You really should do both checks.

Resources