Why NSTimer doesn't stop with invalidate? - objective-c

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.

Related

Add continuation block to method

I have a singleton that displays status messages:
+ (FFStatusDisplay*) sharedInstance
{
static FFStatusDisplay* sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,
^{
sharedInstance = [[FFStatusDisplay alloc] init];
});
return sharedInstance;
}
- (void) showStatus:(NSString*)message Timeout:(float)timeout Controller:(UIViewController*)controller
{
if (![self isDisplaying])
{
[self setIsDisplaying:YES];
[self setStatusAlert:[UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]];
[controller presentViewController:[self statusAlert] animated:YES completion:
^{
[NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:#selector(timerExpired:) userInfo:nil repeats:NO];
}];
}
}
- (void)timerExpired:(NSTimer *)timer
{
[[self statusAlert] dismissViewControllerAnimated:YES completion:nil];
[self setStatusAlert:nil];
[self setIsDisplaying:NO];
}
I would like to modify showStatus so that I can optionally call it with a completion block that executes after the message is removed by the timer. I think the declaration should be something like this:
- (void) showStatus:(NSString*)message Timeout:(float)timeout Controller:(UIViewController*)controller withBlock:(nullable id (^)(void))block;
But I don't know how to call the block in the body of the implementation. What I've been trying isn't right:
{
if (![self isDisplaying])
{
[self setIsDisplaying:YES];
[self setStatusAlert:[UIAlertController alertControllerWithTitle:nil message:message preferredStyle:UIAlertControllerStyleAlert]];
[controller presentViewController:[self statusAlert] animated:YES completion:
^{
[NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:#selector(timerExpired:) userInfo:nil repeats:NO];
}];
}
block; //this isn't right
}
Can someone show me how to do this?
Thanks
In Objective-C, you call a block like a C function:
block();
That being said, if you want it to fire after your timer has fired, you'll need to add some mechanism to hang onto the block and call it from your timerExpired: method

Why doesn't my UIActivityIndicator stop spinning?

I have the code below displaying an ActivityIndicator before calling a GCD process. The background process throws a Notification when it's complete or encounters an error. I am calling the stopAnimating method in the error handler but the spinner keeps spinning. Why?
UIActivityIndicatorView *mIndicator;
#interface VC_Main ()
#end
- (void)viewDidLoad
{
[super viewDidLoad];
[NSLog(#"viewDidLoad");
// create indicator for download activity
mIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[mIndicator setCenter:CGPointMake([self getScreen].x /2.0, [self getScreen].y / 2.0)]; // landscape mode
[self.view addSubview:mIndicator];
// fire off a single interval
[NSTimer scheduledTimerWithTimeInterval:0.0
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:NO];
...
}
- (void) timerTask:(NSTimer *) timer
{
NSLog(#"DEBUG: timertask timeout");
[mIndicator startAnimating];
...
}
// if there is an error parsing xml downloaded from server, it notifies here
- (void) xmlError:(NSNotification *)note
{
NSLog(#"error parsing xml");
[mIndicator stopAnimating]; // this doesn't work
// fire off a refresh using retry timeout
[NSTimer scheduledTimerWithTimeInterval:TIMEOUT_RETRY_MINS
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:NO];
NSLog(#"will retry in %d", TIMEOUT_RETRY_MINS);
}
As with every UIKit call, you need to do it on the main thread.
Just do:
dispatch_async(dispatch_get_main_queue(), ^{
[mIndicator stopAnimating];
});
and it should work
Perhaps you retry too soon?
scheduledTimerWithTimeInterval:TIMEOUT_RETRY_MINS
It's supposed to be seconds, not minutes.

how to finish my countdown timer in objective c?

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

Trouble setting label text in iOS

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.

Check if an animation image is in an image view at a given time

I have an image view that has two animation images, occuring in 1-second intervals. I want to run some methods when my image view is displaying one of those two images
I already tried doing:
if(self.imageViewThatPerformsAnimation.image == [UIImage imageNamed: #"someImage"])
[self doSomeMethod];
but when I tried this and ran it, [self doSomeMethod]; always ran, and not just when the image view was displaying that one image.
I'm thinking about having a timer that changes a boolean value every one second then saying
if (booleanValue==YES)
[self doSomeMethod]
It's just that I feel there may be a better way.
If you wanted to use a NSTimer, it might look like:
#interface MyViewController ()
{
NSTimer *_timer;
NSArray *_images;
NSInteger _currentImageIndex;
}
#end
#implementation MyViewController
#synthesize imageview = _imageview;
- (void)viewDidLoad
{
[super viewDidLoad];
_images = [NSArray arrayWithObjects:
[UIImage imageNamed:#"imgres-1.jpg"],
[UIImage imageNamed:#"imgres-2.jpg"],
[UIImage imageNamed:#"imgres-3.jpg"],
[UIImage imageNamed:#"imgres-4.jpg"],
nil];
_currentImageIndex = -1;
[self changeImage];
// Do any additional setup after loading the view.
}
- (void)changeImage
{
_currentImageIndex++;
if (_currentImageIndex >= [_images count])
_currentImageIndex = 0;
self.imageview.image = [_images objectAtIndex:_currentImageIndex];
if (_currentImageIndex == 0)
[self doSomething];
}
- (void)startTimer
{
if (_timer) {
[_timer invalidate];
_timer = nil;
}
_timer = [NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(changeImage)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
}
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startTimer];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self stopTimer];
}