I m using timer on circular progress bar and taken NSDate property in interface (tested also taking in viewWillAppear) but when viewController loads it show starting from 27 seconds remain, while i have set its value to 30 seconds.
My scenario is, a user has a question and optional answers and he/she has some time suppose 30 seconds when he/she click start button than another screen opens after this screen loads time start should be from 30 seconds.
#interface TestViewController ()
{
NSTimeInterval totalCountdownInterval;
NSDate* startDate;
}
}
-(void)viewWillAppear:(BOOL)animated{
[self startTimer];
totalCountdownInterval = 30.0;
startDate = [NSDate date];
}
-(void)startTimer {
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
}
}
-(void)stopTimer {
if ([_timer isValid]) {
[_timer invalidate];
}
_timer = nil;
}
-(void)timerFired:(NSTimer *)timer {
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSTimeInterval remainingTime = totalCountdownInterval - elapsedTime;
CGFloat timeRemain = remainingTime;
NSLog(#"%f", timeRemain);
if (remainingTime < 0.0) {
[_timer invalidate];
}
[_circleProgressBar setHintTextGenerationBlock:(^NSString *(CGFloat progress) {
if (!(timeRemain < 0.9)) {
progress = timeRemain - 1;
return [NSString stringWithFormat:#"%.0f", progress];
}else{
progress = 0;
return [NSString stringWithFormat:#"%.0f", progress];
}
})];
[_circleProgressBar setProgress:(_circleProgressBar.progress + 0.0333f) animated:YES];
}
-(void) viewWillDisappear:(BOOL)animated {
[self stopTimer];
}
Any help will be appreciated. Thanks.
The first timer event will be fired after 1 second is passed, so you should see 29 first. So you should display the number 30 manually somehow, outside the timer event.
And you subtract 1 in your code in setHintTextGenerationBlock :
progress = timeRemain - 1;
which will make you see 28 as initial.
And as final you should start timer in viewDidAppear instead of viewWillAppear where you lose another second and come to 27.
To correct code should be like:
#interface TestViewController ()
{
NSTimeInterval totalCountdownInterval;
NSDate* startDate;
}
}
-(void)viewDidLoad {
[_circleProgressBar setHintTextGenerationBlock:(^NSString *(CGFloat progress) {
return [NSString stringWithFormat:#"%.0f", progress * 30];
})];
[_circleProgressBar setProgress:1.0) animated:NO];
}
-(void)viewDidAppear:(BOOL)animated{
[self startTimer];
totalCountdownInterval = 30.0;
startDate = [NSDate date];
}
-(void)startTimer {
if (!_timer) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:YES];
}
}
-(void)stopTimer {
if ([_timer isValid]) {
[_timer invalidate];
}
_timer = nil;
}
-(void)timerFired:(NSTimer *)timer {
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSTimeInterval remainingTime = totalCountdownInterval - elapsedTime;
CGFloat timeRemain = remainingTime;
NSLog(#"%f", timeRemain);
if (remainingTime < 0.0) {
[_timer invalidate];
}
[_circleProgressBar setProgress:(1.0 * timeRemain / 30.0) animated:YES];
}
-(void) viewWillDisappear:(BOOL)animated {
[self stopTimer];
}
You should set progress to 1.0 in the beginning and count down to 0.0 in 30 steps.
Set hint generation block to display something meaningful for 1.0 - 0.0 as 30 - 0.
use that timer code in void view did appear and try once.
- (void)viewDidAppear:(BOOL)animated
{
[self startTimer];
totalCountdownInterval = 30.0;
startDate = [NSDate date];
}
Related
I am creating a stopwatch and the start and stop functionality is not coordinated. When the timer stops, it doesn't start where it left of.
The code looks like this:
self.endDate = [NSDate dateWithTimeIntervalSinceNow:12.0f*60.0f];
-(void)startPressed:(id)sender{
if(!_running){
_running = TRUE;
[sender setTitle:#"Stop" forState:UIControlStateNormal];
if (_stopTimer == nil) {
_stopTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(changeTimer:)
userInfo:nil
repeats:YES];
}
}else{
_running = FALSE;
[sender setTitle:#"Start" forState:UIControlStateNormal];
[_stopTimer invalidate];
_stopTimer = nil;
}
}
- (void)changeTimer:(NSTimer*)timer {
_timeInterval = [self.endDate timeIntervalSinceNow];
self.timerControl1.minutesOrSeconds = ((NSInteger)_timeInterval)%60;
self.timerControl2.minutesOrSeconds = (NSInteger)(_timeInterval/60.0f);
self.timerControl3.minutesOrSeconds = ((NSInteger)_timeInterval)%60;
}
I know it is because of timeIntervalSinceNow. But, how to change this? Need some guidance on this.
The problem:
When you stop the stopwatch and start it again after 3s for example, it shld still start back from the same timing. but both my code, the start and stop timing are different, they are starting from a different timing. Need help to solve this.
don't use the endDate in changeTimer but use the endDate only to init the timer
#import "RimsViewController.h"
#interface RimsViewController ()
#property(weak) IBOutlet UILabel *label;
#property(strong) NSDate *endDate;
#end
#implementation RimsViewController {
BOOL _running;
NSTimeInterval _timeInterval;
NSTimer *_stopTimer;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.endDate = [NSDate dateWithTimeIntervalSinceNow:10*60];
_timeInterval = [self.endDate timeIntervalSinceNow];
}
-(IBAction)startPressed:(id)sender{
if(!_running){
_running = TRUE;
[sender setTitle:#"Stop" forState:UIControlStateNormal];
if (_stopTimer == nil) {
_stopTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(changeTimer:)
userInfo:nil
repeats:YES];
}
}else{
_running = FALSE;
[sender setTitle:#"Start" forState:UIControlStateNormal];
[_stopTimer invalidate];
_stopTimer = nil;
}
}
- (void)changeTimer:(NSTimer*)timer {
_timeInterval -= 1;// [self.endDate timeIntervalSinceNow];
NSUInteger secs = _timeInterval;
int h = (NSUInteger)secs/3600;
secs-=h*3600;
int m = (NSUInteger)secs/60;
secs-=m*60;
int s = secs;
self.label.text = [NSString stringWithFormat:#"%d / %d / %d", h, m, s];
}
#end
I discovered an issue with my sample project. I simply create a scheduledTimer that "animate" a label and then, when I reached the result I want, I invalidate the timer and I set an another one, this time as a "clock".
This is the code I use
//
// ViewController.m
//
//
//
//
//
#import "ViewController.h"
#define ANIMATION_INTERVAL 0.07 // in secondi
#define ANIMATION_DURATION 1 // in secondi
#interface ViewController ()
{
int contatore;
NSString *hour;
NSString *minute;
NSString *second;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
contatore = 0;
[self startTimeAnimation];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)startTimeAnimation
{
NSTimer * animationTimer = [NSTimer scheduledTimerWithTimeInterval:ANIMATION_INTERVAL target:self selector:#selector(timeout:) userInfo:nil repeats:YES];
}
-(void)timeout: (NSTimer *)timer
{
// Simulate of the Timer Duration with a counter
if (contatore < ceilf(ANIMATION_DURATION/ANIMATION_INTERVAL))
{
// Proceed with animation
contatore++;
int tempHour = arc4random_uniform(24);
int tempMinute = arc4random_uniform(60);
int tempSecond = arc4random_uniform(60);
if (tempHour < 10)
{
hour = [NSString stringWithFormat:#"0%d", tempHour];
}
else
{
hour = [NSString stringWithFormat:#"%d", tempHour];
}
if (tempMinute < 10)
{
minute = [NSString stringWithFormat:#"0%d", tempMinute];
}
else
{
minute = [NSString stringWithFormat:#"%d", tempMinute];
}
if (tempSecond < 10)
{
second = [NSString stringWithFormat:#"0%d", tempSecond];
}
else
{
second = [NSString stringWithFormat:#"%d", tempSecond];
}
_orarioLbl.text = [NSString stringWithFormat:#"%#:%#:%#", hour, minute, second];
}
else
{
// Stops animation
[timer invalidate];
[timer release];
contatore = 0;
[self setActualTime];
// Starts clock
NSTimer *clockTimer = [NSTimer timerWithTimeInterval:.5f target:self selector:#selector(updateClock) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:clockTimer forMode:NSRunLoopCommonModes];
}
}
-(void)updateClock
{
[self setActualTime];
}
-(void)setActualTime
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH"];
hour = [dateFormatter stringFromDate:[NSDate date]];
[dateFormatter setDateFormat:#"mm"];
minute = [dateFormatter stringFromDate:[NSDate date]];
[dateFormatter setDateFormat:#"ss"];
second = [dateFormatter stringFromDate:[NSDate date]];
_orarioLbl.text = [NSString stringWithFormat:#"%#:%#:%#", hour, minute, second];
[dateFormatter release];
}
#end
Since I start the "clock", memory still stays on 19/20 MB with constant persistent allocation. When the timer updates the minute value, persistent allocations increase as you can see in the gif! How is it possible? However, also 19MB of memory is too much for a simple clock, isn't it?
Profiling on Instruments, the new allocations are all about CoreGraphics!
EDIT I tested it on another device and the persistent allocations decreased. I don't know why, but I solved in this way. Thanks
NSTimer retains its target and that may lead to a retain cycle. Grab a weak reference to self and set that as the target:
__weak typeof(self) weakSelf = self;
NSTimer * animationTimer = [NSTimer scheduledTimerWithTimeInterval:ANIMATION_INTERVAL target:weakSelf selector:#selector(timeout:) userInfo:nil repeats:YES];
and
__weak typeof(self) weakSelf = self;
NSTimer *clockTimer = [NSTimer timerWithTimeInterval:.5f target:weakSelf selector:#selector(updateClock) userInfo:nil repeats:YES];
May I also recommend you create a property for the dateFormatter, as NSDateFormatters are expensive to create. And why are you releasing the dateFormatter? Are you not using ARC?
I have an NSTimer which I pause and resume with the following methods respectively:
-(void) pauseTimer:(NSTimer *)timer {
self.scene.paused = YES;
pauseStart = [NSDate date];
previousFireDate = [timer fireDate];
[timer setFireDate:[NSDate distantFuture]];
}
-(void) resumeTimer:(NSTimer *)timer {
self.scene.paused = NO;
NSDate *currentDate = [NSDate date];
NSTimeInterval pauseTime = [currentDate timeIntervalSinceDate:pauseStart];
NSDate *neededFireDate = [NSDate dateWithTimeInterval:pauseTime sinceDate:previousFireDate];
[timer setFireDate:neededFireDate];
NSLog(#"Fire Date: %#", timer.fireDate); //returning null
}
It is not properly resetting the fire date because when I check timer.fireDate, it says null. All we want is for these methods to pause the timer and resume the timer where it left off when the resume method is called. Any help is greatly appreciated.
My solution differs from yours in one major thing. I recreate a timer when it resumes. Here is how I do it and it's been working quite well:
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, assign) NSTimeInterval elapsedTime;
#property (nonatomic, strong) NSDate *startDate;
- (void)startTimer
{
NSTimeInterval timeInterval = 20.0f;
// Create a timer. Adjust the elapsed time.
_timer = [NSTimer scheduledTimerWithTimeInterval:(timeInterval - _elapsedTime)
target:self
selector:#selector(timerFireMethod:)
userInfo:nil
repeats:YES];
// Save the start date.
_startDate = [NSDate date];
}
- (void)timerFireMethod:(NSTimer *)theTimer
{
// Clean.
[_timer invalidate];
_timer = nil;
_elapsedTime = 0.0;
[self startTimer];
// Additional handling.
}
- (void)pauseTimer
{
// Clean.
[_timer invalidate];
_timer = nil;
// Save the elapsed time.
_elapsedTime = [[NSDate date] timeIntervalSinceDate:_startDate];
}
Hope it helps.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to Pause/Play NSTimer?
I have three buttons start stop and pause..My start and stop button is working fine with the below code..but when press Pause it pause the timer..but when again i Press start.IT continues from the new added time ...not from the pause time....
supoose i pause at 5 second of start and wait for 5 sec then press start...it should display 5 ...but displaying 10..
because I have not mentioned (timer:) in timer!=nill of start...
how it will be add..
I have problems:
Pause not working.
-(void)start:(NSTimer *)timer
{
if(_timer==nil)
{
startDate =[NSDate date];
_timer=[NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(timer:) userInfo:nil repeats:YES];
}
if(_timer!=nil)
{
float pauseTime = -1*[pauseStart timeIntervalSinceNow];
[_timer setFireDate:[previousFireDate initWithTimeInterval:pauseTime sinceDate:previousFireDate]];
}
}
-(void)timer:(NSTimer *)timer
{
NSInteger secondsSinceStart = (NSInteger)[[NSDate date] timeIntervalSinceDate:startDate];
NSInteger seconds = secondsSinceStart % 60;
NSInteger minutes = (secondsSinceStart / 60) % 60;
NSInteger hours = secondsSinceStart / (60 * 60);
NSString *result = nil;
if (hours > 0)
{
result = [NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
}
else
{
result = [NSString stringWithFormat:#"%02d:%02d", minutes, seconds];
}
label.text=result;
NSLog(#"time interval -> %#",result);
}
-(void)stop
{
if(_timer!=nil)
{
startDate=nil;
[_timer invalidate];
_timer = nil;
}
}
-(void)pause:(NSTimer *)timer
{
pauseStart = [NSDate dateWithTimeIntervalSinceNow:0];
previousFireDate = [_timer fireDate];
[_timer setFireDate:[NSDate distantFuture]];
}
-(void)pause:(NSTimer *)timer
{
pauseStart = [NSDate dateWithTimeIntervalSinceNow:0];
previousFireDate = [_timer fireDate];
//[timer setFireDate:[NSDate distantFuture]];
[_timer setFireDate:[NSDate distantFuture]];
}
Here is the code that I used for a timer:
I store all the components for the time, in my (singleton) class, the hour, minute, seconds value.
And, I simply "invalidate" the timer in the "pause" method, AND I store the values.
See, if this helps.
-(void) startTimer
{
NSLog(#"Values for timer: %d H, %d M, %d S", self.hours, self.minutes, self.seconds);
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateTimer) userInfo:nil repeats:YES];
}
-(void) pauseTimer
{
if(_timer)
{
[_timer invalidate];
}
_timer = nil;
self.hour = hourValue;
self.minute = minuteValue;
self.second = secondsValue;
}
I am using NSTimer to make a stopwatch. I would like it to continue working if the user switches to a different app, but right now it only works when the app is running. I figure that this is probably pretty simple to fix by recording a timestamp when the timer starts, but I'm not really sure how to reconcile that with the ability to pause/restart the timer in the middle.
My code:
-(IBAction)start;
{
if(sharedInstance.timer == nil){
sharedInstance.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
sharedInstance.timerRunning = YES;
}
}
-(IBAction)stop;
{
[sharedInstance.timer invalidate];
sharedInstance.timer = nil;
sharedInstance.timerRunning = NO;
}
-(void)showActivity;
{
sharedInstance.elapsedTime += 1;
int hours = sharedInstance.elapsedTime / 3600 ;
int minutes = sharedInstance.elapsedTime / 60 - hours * 60;
int seconds = sharedInstance.elapsedTime - hours * 3600 - minutes * 60;
sharedInstance.timeString = [NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
self.readout.text = sharedInstance.timeString;
}
-(void)viewDidLoad {
sharedInstance = [DataSingleton sharedInstance];
if (sharedInstance.timeString==nil) {
sharedInstance.timeString=#"00:00:00";
}
self.readout.text = sharedInstance.timeString;
[sharedInstance.timer invalidate];
sharedInstance.timer = nil;
if(sharedInstance.timerRunning) {
[self start];
}
[super viewDidLoad];
}
-(void)reset {
[self stop];
sharedInstance.elapsedTime = 0;
sharedInstance.timeString = #"00:00:00";
self.readout.text = sharedInstance.timeString;
}
Every time you call showActivity you could get a new time stamp and subtract the old one from it getting the change in time. Then add that to elapsedTime instead of 1.