I have a NSTimer that works fine and counts down in 1 second intervals. But I want the timer to trigger immediately without that 1 second delay.
I thought calling [timer fire] should work for this (described here) but it doesn't make a difference. I want the timer to be triggered as fast as if I scheduled the interval to be 0.
- (void)onStartButtonPressed:(UIButton*)sender
{
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:tv];
NSIndexPath* indexPath = [tv indexPathForRowAtPoint:buttonPosition];
NSInteger index = indexPath.row;
// starts timer for cell at the index path
if (indexPath != nil)
{
NSTimer* timer = [timerArray objectAtIndex: index];
if ([timer isEqual:[NSNull null]]) {
NSLog(#"It's empty");
// start timer
NSTimer timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(onTick:)
userInfo:indexPath
repeats:YES];
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSLog(#"before timer fire");
[timer fire];
// update data array of timer objects and countdowns
NSInteger selectedTimeIdx = [[selectedTimeIdxArray objectAtIndex: index] integerValue];
NSInteger selectedTime = [pickerTimeArray[selectedTimeIdx] integerValue];
[timerArray replaceObjectAtIndex:index withObject:timer];
[countdownArray replaceObjectAtIndex:index withObject:[NSNumber numberWithInteger:selectedTime*60]];
} else {
NSLog(#"It's not empty");
}
}
- (void)onTick:(NSTimer *)timer
{
NSLog(#"on tick method starts");
// get the timer's owner's index path and update label
NSIndexPath* indexPath = [timer userInfo];
NSInteger index = indexPath.row;
// update countdown
NSInteger countdown = [[countdownArray objectAtIndex: index] integerValue];
[countdownArray replaceObjectAtIndex:index withObject:[NSNumber numberWithInteger:--countdown]];
// NSLog(#"countdown: %ld", (long)countdown);
[tv reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
// NSLog(#"Tic indexPath: %#", indexPath);
if (countdown == 0)
{
[timer invalidate];
timer = nil;
}
}
The timer works but I don't want there to be a 1 second delay for it to be initially triggered. I want the timer to start immediately.
Edit: I added logs that rmaddy suggested. Here are my results (I changed the interval time to 3):
2015-05-19 14:41:02.827 restaurant[4206:77915] before timer fire
2015-05-19 14:41:02.827 restaurant[4206:77915] on tick method starts
2015-05-19 14:41:05.827 restaurant[4206:77915] on tick method starts
2015-05-19 14:41:08.828 restaurant[4206:77915] on tick method starts
Why not just call your method before the timer
[self onTick:nil];
//usual timer code here
Edit: as stated by rmaddy
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(onTick:)
userInfo:indexPath repeats:YES];
[self onTick:timer];
Some of the comments were really helpful, especially adding the logs as suggested by rmaddy. It turns out [timer fire] was working fine.
I had confused myself because my timerLabel which displayed the countdown was being updated in my cell with a 1 second delay, and I thought that meant the timer instantiation was delayed.
Once I saw what the problem really was, all I had to do was update the cell in the same block that I instantiated the timer (instead of just onTick).
I just added this to my onStartButtonPressed and everything works as I want it to.
// update countdown
NSInteger countdown = [[countdownArray objectAtIndex: index] integerValue];
[countdownArray replaceObjectAtIndex:index withObject:[NSNumber numberWithInteger:--countdown]];
// cell label gets updated right at start button press
[tv reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
Leaving here in case it helps anyone.
Related
I make a Cocoa application.I create a NSTimer,it can work,but can't stop with invalidate.The following is my code.Anyone else can help me?
#implementation Document
{
NSTimeInterval elapsedTime;
NSTimer *timer;
}
- (IBAction)start:(id)sender
{
elapsedTime = 0.002f;
dispatch_async(dispatch_get_main_queue(), ^{
if(timer == nil)
{
NSLog(#"Starting");
captureFrame = [[CaptureFrame alloc] init];//captureFrame is another object
timer = [NSTimer scheduledTimerWithTimeInterval:elapsedTime target:captureFrame selector:#selector(sendSingleImageImage) userInfo:nil repeats:YES];
NSThread *thread = [NSThread currentThread];
NSLog(#"Current thread is%#",thread);// I want to see the thread.
}
else
{
NSLog(#"Exist!");
[timer invalidate];
timer = nil;
}
});
[_stopButton setEnabled:YES];
[_startButton setEnabled:NO];
}
- (IBAction)stop:(id)sender
{
dispatch_async(dispatch_get_main_queue(), ^{
[timer invalidate];
timer = nil;
NSThread *sthread = [NSThread currentThread];
NSLog(#"S thread is %#",sthread);// I want to see the thread
});
[_startcastButton setEnabled:YES];
[_stopButton setEnabled:NO];
NSLog(#"Stopping");
}
I tried use the following method,but didn't work.
dispatch_async(dispatch_get_main_queue(), ^(void)block)
I also tried to find whether two actions in the same thread,and the result is yes.
So please tell me how to stop the NSTimer.
Any help is appreciated,thanks a lot in advance.
I have a generated Subview that can be moved around. Every time it moves I check if it's passed 300 on the X-axis. My problem is, that when it passes the point and you don't stop moving it, the NSTimer gets started so often, that the program crashes.
NSArray *subviews = [self.view subviews];
for (UIView *subview in subviews) {
if (subview.frame.origin.x > 300) {
NSMutableArray *data = [[NSMutableArray alloc] initWithCapacity:1];
[data addObject:subview];
[NSTimer scheduledTimerWithTimeInterval:3.00 target:self selector:#selector(callFunction:) userInfo:data repeats:NO];
}
}
You should keep a reference to NSTimer instances and when you don't want it to be fired anymore you can call -[NSTimer invalidate]
Update besides, is it your intentions to schedule timers in a loop?
Add a property where you save a reference to the timer. So you can always check if NSTimer is already started and you stop it by
[NSTimer invalidate];
you add a property #property(nonatomic, strong) NSTimer *timerand then you can check like:
if(!self.timer){
self.timer = [NSTimer scheduledTimerWithTimeInterval:3.00 target:self selector:#selector(callFunction:) userInfo:data repeats:NO];
}
Try This :
[NSTimer invalidate];
NSTimer = nil;
I have a countdown timer where the user can input the time they want to start from using a countdown timer like in the clock app. The problem is, I can't figure out how to make the timer actually count down. I have already made the UI and have most of the code, but I don't know what would go in the updateTimer method I have. Here is my code:
- (void)updateTimer
{
//I don't know what goes here to make the timer decrease...
}
- (IBAction)btnStartPressed:(id)sender {
pkrTime.hidden = YES; //this is the timer picker
btnStart.hidden = YES;
btnStop.hidden = NO;
// Create the timer that fires every 60 sec
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
}
- (IBAction)btnStopPressed:(id)sender {
pkrTime.hidden = NO;
btnStart.hidden = NO;
btnStop.hidden = YES;
}
Please let me know what goes in the updateTimer method to let the timer decrease.
Thanks in advance.
You would track the overall time left with a variable. The updateTimer method will be called every second, and you would reduce the time left variable by 1 (one second) each time updateTimer method is called. I have given an example below, but I have renamed updateTimer to reduceTimeLeft.
SomeClass.h
#import <UIKit/UIKit.h>
#interface SomeClass : NSObject {
int timeLeft;
}
#property (nonatomic, strong) NSTimer *timer;
#end
SomeClass.m
#import "SomeClass.h"
#implementation SomeClass
- (IBAction)btnStartPressed:(id)sender {
//Start countdown with 2 minutes on the clock.
timeLeft = 120;
pkrTime.hidden = YES;
btnStart.hidden = YES;
btnStop.hidden = NO;
//Fire this timer every second.
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(reduceTimeLeft:)
userInfo:nil
repeats:YES];
}
- (void)reduceTimeLeft:(NSTimer *)timer {
//Countown timeleft by a second each time this function is called
timeLeft--;
//When timer ends stop timer, and hide stop buttons
if (timeLeft == 0) {
pkrTime.hidden = NO;
btnStart.hidden = NO;
btnStop.hidden = YES;
[self.timer invalidate];
}
NSLog(#"Time Left In Seconds: %i",timeLeft);
}
- (IBAction)btnStopPressed:(id)sender {
//Manually stop timer
pkrTime.hidden = NO;
btnStart.hidden = NO;
btnStop.hidden = YES;
[self.timer invalidate];
}
#end
I have a connected UILabel
#property (strong, nonatomic) IBOutlet UILabel *label;
And an Action, which is triggered by the button
- (IBAction)buttonPressed:(UIButton *)sender;
When button is pressed, i'd like to update the label to display running seconds up to 3 minutes, so i
- (IBAction)buttonPressed:(UIButton *)sender {
for (int i =0; i < 180; ++i) {
[label setText:[NSString stringWithFormat:#"%d", i]];
sleep(1);
}
}
Confirmed, method is called, timer is ticking ..the label text however does not change. What am i doing wrong please?
The sleep() does not allow the UI thread to update itself.
Here is a sample using GCD that closely matches you original code. Note: there are better ways to do this (see: dispatch_after()).
- (IBAction)buttonPressed:(UIButton *)sender {
[label setText:[NSString stringWithFormat:#"%d", 0]];
dispatch_queue_t queue = dispatch_queue_create("com.test.timmer.queue", 0);
dispatch_async(queue, ^{
for (int i = 1; i < 180; ++i) {
sleep(1);
dispatch_async(dispatch_get_main_queue(), ^{
[label setText:[NSString stringWithFormat:#"%d", i]];
});
});
}
your sleep() is in the main thread , your view cannot refresh , you can ues NSTimer to do it.
- (void)update
{
[label setText:[NSString stringWithFormat:#"%d", i]];
i++;
if(i>=100)
{
[timer invalidate];
}
}
- (IBAction)buttonPressed:(UIButton *)sender
{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(update) userInfo:nil repeats:YES];
[timer fire];
}
You have to exit back to your runloop to allow the control to refresh. You should probably use an NSTimer that fires every second or so instead of a tight loop.
Although not really recommended, the following might also work if you call it right after setText:
[[NSRunLoop currentRunLoop] acceptInputForMode:NSDefaultRunLoopMode beforeDate:nil];
All UI related things are done in the main thread. Forcing your runtime to sleep on the main thread will literally freeze your UI. Do it 180 times and you got one frustrated end-user.
I have NSTimer to display an image after 2 seconds when i click on button. It is working fine when I Click on a Button, But the problem is When I double Click the Button the NSTimer is Not Stopping. Continuously it is displaying images(calling NSTimer method) without Clicking on the button for Next Time. How to stop the NSTimer when i double Click / more Clicks on that button at a time.
*This is my Code*
-(void)buttonClicked
{
timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(timerClicked) userInfo:nil repeats:YES];
imgviewtransparent.image = [UIImage imageNamed:[imagesArray objectAtIndex:i]];
}
-(void)timerClicked
{
[timer invalidate];
timer = nil;
}
With double Click it will schedule timer twice.
You can create a BOOL property to avoid this if you want.
-(void)viewDidLoad{
[super viewDidLoad];
//Define all your implementation
//Set BOOL property to no at start
self.isButtonClicked = NO; // You need to define this BOOL property as well.
}
-(void)buttonClicked {
// Return if one click is already in process
if (self.isButtonClicked) {
return;
}
self.isButtonClicked = YES:
timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self
selector:#selector(timerClicked) userInfo:nil repeats:YES];
imgviewtransparent.image = [UIImage imageNamed:[imagesArray objectAtIndex:i]];
}
-(void)timerClicked {
[timer invalidate];
timer = nil;
self.isButtonClicked = NO;
}