Stop immediately NSTimer - objective-c

I'm trying to stop my NSTimer (no repeats) immediately but i can't.
my NSTimer do the following code:
-(void)timerAction:(NSTimer*)timer{
dispatch_queue_t serverDelaySimulationThread = dispatch_queue_create("com.xxx.serverDelay", nil);
dispatch_async(serverDelaySimulationThread, ^{
[NSThread sleepForTimeInterval:2.0];
dispatch_async(dispatch_get_main_queue(), ^{
//CALL WEBSERVICE RESPONSE IN HERE
});
});
}
Each time textField Did Change, i will invalidate NSTimer and recreate NSTimer!! All i want to execute a request to WEBSERVICES if the Delay time between 2 times user typing characters bigger than 2s. My code is following
-(void) textFieldDidChange:(id)sender{
[MyTimer invalidate];
MyTimer = nil;
MyTimer = [NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(timerAction:) userInfo:nil repeats:NO];
}
But, when i quickly type "ABCD" -> 2s after that, 4 webservices was called.
How can i stop NSTimer while it unfinished
Tks for advance!

-(BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
[myTimer invalidate];
myTimer = nil;
myTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(timerFired) userInfo:nil repeats:NO];
return YES;
}
This worked for me.

Your timer timeInterval is set to 0. So each time you type your timer fires. Change your code to look like this
MyTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self selector:#selector(timerAction:) userInfo:nil repeats:NO];
And it should work :)

The problem is you are implementing textdidchange delegate. So how many times you type text. It will get called. SO inside that you have written four letters. So definitely your NSTimer is calling four times your web service. I would suggest better to use - (void)controlTextDidEndEditing:(NSNotification *)aNotification So that whenever you enter or tabb out your webservice will called.

Related

Why NSTimer sends positions every 5 seconds instead of every 60?

Why NSTimer sends positions every 5 seconds instead of every 60?
- (void)startTimer {
self.timer = [NSTimer scheduledTimerWithTimeInterval:60.0
target:self
selector:#selector(sendPosition)
userInfo:nil
repeats:YES];
}
- (void)stopTimer {
if(self.timer){
[self.timer invalidate];
self.timer = nil;
}
}
I suspect that there are multiple timers created due to multiple firing of startTimer function. To ensure that there is only one instance of such timer, you can implement the following.
- (void)startTimer {
// stop and remove timer first if it is already there
if(self.timer){
[self.timer invalidate];
self.timer = nil;
}
self.timer = [NSTimer scheduledTimerWithTimeInterval:60.0
target:self
selector:#selector(sendPosition)
userInfo:nil
repeats:YES];
}
This way, no matter how many times the startTimer was called,there is only one instance of it.

How can I stop a NSTimer from being called over and over again

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;

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 run NSTimer until flag variable ON in Cocoa

I have a problem: I want to NSTimer waiting until FLAG variable is YES, if FLAG = YES, myTimer is stop. How can i do that? I tried below code:
NSTimer *myTimer;
int delay = 6.0;
scanTimer= [NSTimer scheduledTimerWithTimeInterval:6.0 target:self selector:#selector(anotherfunc) userInfo:nil repeats:YES];
myTimer= [NSTimer timerWithTimeInterval: delay
target:self
selector: #selector(resetAll:) userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSModalPanelRunLoopMode];
[[NSApplication sharedApplication] runModalForWindow: scanningPanel];
This is resetAll () function :
-(void) resetAll: (NSTimer *) theTimer
{
if(FLAG)
{
NSLog(#"killWindow");
[[NSApplication sharedApplication] abortModal];
[scanningPanel orderOut: nil];
FLAG = NO;
}
else
{
delay +=6.0;
myTimer= [NSTimer timerWithTimeInterval: delay
target:self
selector: #selector(resetAll:) userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSModalPanelRunLoopMode];
[[NSApplication sharedApplication] runModalForWindow: scanningPanel];
}
}
I used 2 NSTimer, but only myTimer run, scanTimer not run. Please give me any suggestions. Thanks in advance
Why not use Key Value Observing for the FLAG change? Add the flag variable as a property of the class you're working in:
#property(nonatomic, assign) BOOL flag;
To avoid repetition place in your .m:
#define kFooFlagKey #"flag"
Override -setFlag: so it is KVO compliant:
-(void)setFlag:(BOOL)flag
{
if (_flag == flag) {
return;
}
[self willChangeValueForKey:kFooFlagKey];
_flag = flag;
[self didChangeValueForKey:kFooFlagKey];
}
In the class' initializer add self as the observer for the keypath you'd like to monitor. In this case it will be for the property "flag".
[self addObserver:self
forKeyPath:kFooFlagKey
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:NULL];
Don't forget to remove the observer in the class' -dealloc:
[self removeObserver:self forKeyPath:kFooFlagKey];
Create the timer to fire repeatedly (firingCallBack is a method called with each fire):
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.0f
target:self
selector:#selector(firingCallBack)
userInfo:nil
repeats:YES];
Implement KVO observing method:
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqualToString:kFooFlagKey]) {
if (self.flag) {
[self performSelector:#selector(resetAll) withObject:nil afterDelay:0.0f];
}
}
}
Implement -resetAll however you'd like. It will be called when you are setting the flag variable via the accessor AND the flag is set to YES
Try like this:-
NSTimer *myTimer;
int delay = 6.0;
scanTimer= [NSTimer scheduledTimerWithTimeInterval:6.0 target:self selector:#selector(anotherfunc) userInfo:nil repeats:YES];
myTimer= [NSTimer scheduledTimerWithTimeInterval: delay
target:self
selector: #selector(resetAll:) userInfo:nil
repeats:NO];
[NSApp beginSheet:scanningPanel modalForWindow:[self window]
modalDelegate:self
didEndSelector:nil
contextInfo:self];
- (void)resetAll:(NSTimer *)theTimer
{
if (flag== YES)
{
[NSApp endSheet:scanningPanel];
[scanningPanel orderOut:self];
flag=NO;
}
else
{
delay +=6.0;
myTimer= [NSTimer scheduledTimerWithTimeInterval: delay
target:self
selector: #selector(resetAll:) userInfo:nil
repeats:NO];
[NSApp beginSheet:scanningPanel modalForWindow:[self window]
modalDelegate:self
didEndSelector:nil
contextInfo:self];
}
}
You just start the timer (already scheduled) and make it repeat at a relatively high frequency and stop it when the condition is met. One timer can act as two, like this:
- (void)startTimers {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
}
- (void)timerFired:(NSTimer *)timer {
if (self.stopTimerA && self.stopTimerB) {
[timer invalidate];
} else {
if (!self.stopTimerA)
[self timerAFired];
if (!self.stopTimerB)
[self timerBFired];
}
}
- (void)timerAFired {
// this can be coded like it has it's own timer
// we didn't pass the timer, so we can't invalidate it
// to stop...
self.stopTimerA = YES;
}
- (void)timerBFired {
// same idea here
}

How to pass an argument to a method called in a NSTimer

I have a timer calling a method but this method takes one paramether:
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:#selector(timer) userInfo:nil repeats:YES];
should be
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:#selector(timer:game) userInfo:nil repeats:YES];
now this syntax doesn't seems to be right. I tried with NSInvocation but I got some problems:
timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(timer:game)]];
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval
invocation:timerInvocation
repeats:YES];
How should I use Invocation?
Given this definition:
- (void)timerFired:(NSTimer *)timer
{
...
}
You then need to use #selector(timerFired:) (that's the method name without any spaces or argument names, but including the colons). The object you want to pass (game ?) is passed via the userInfo: part:
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval
target:self
selector:#selector(timerFired:)
userInfo:game
repeats:YES];
In your timer method, you can then access this object via the timer object's userInfo method:
- (void)timerFired:(NSTimer *)timer
{
Game *game = [timer userInfo];
...
}
As #DarkDust points out, NSTimer expects its target method to have a particular signature. If for some reason you can't conform to that, you can instead use an NSInvocation as you suggest, but in that case you need to fully initialise it with the selector, target and arguments. Eg:
timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(methodWithArg1:and2:)]];
// configure invocation
[timerInvocation setSelector:#selector(methodWithArg1:and2:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&arg1 atIndex:2]; // argument indexing is offset by 2 hidden args
[timerInvocation setArgument:&arg2 atIndex:3];
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval
invocation:timerInvocation
repeats:YES];
Calling invocationWithMethodSignature on its own doesn't do all that, it just creates an object that is able to be filled in in the right manner.
You can pass NSDictionary with named objects (like myParamName => myObject) through userInfo parameter like this
theTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval
target:self
selector:#selector(timer:)
userInfo:#{#"myParamName" : myObject}
repeats:YES];
Then in timer: method:
- (void)timer:(NSTimer *)timer {
id myObject = timer.userInfo[#"myParamName"];
...
}