My NSTimer (startTimer) works fine. It runs the selected method (runTimer) but whatever code I place in the (runTimer) it does not increment. For example if I run the code as below it prints out 5 times but does not increment x. Any ideas - thanks
- (void)startTimer {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runTimer:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
}
- (void)runTimer:(NSTimer *)aTimer {
int x;
x++;
NSLog(#"int x = %i",x);
}
You get a new x every time runTimer is called. If you make it static
static int x;
it'll act the way you expect. For cleanliness, I'd also initialize it:
static int x = 0;
Be careful with static variables in Objective-C methods as suggested by smparkes. They're shared between all instances of that class, so if you've got multiple instances of whatever object this code is from, his answer won't act the way you expect. You'd be better off with an instance variable, because each instance will have its own variable, without affecting other instances:
In your .h:
#interface MyClass : NSObject
{
int x;
}
Then in your -runTimer: method:
- (void)runTimer:(NSTimer *)aTimer {
NSLog(#"int x = %i", x++);
}
If you are guaranteed that there will only be one instance of whatever this class is (e.g. it's a singleton), a static variable inside the -runTimer: method will work, but I'd recommend using an instance variable or #property as it's better programming practice.
runTimer declares x every time, i.e. it creates a new variable, set to 0, increments it and prints it out, so it will always be the same value. you need a variable outside the scope of that method in order to increment it
Related
I have a problem with one property in one class. I am coding with iOS 6.1 if it makes any difference.
The class is UIViewController and the property is the declared in the header file like so:
// Keeps track of time in seconds
#property (nonatomic, strong) NSNumber *timeInSeconds;
In my implementation file I use the property during 3 occasions:
one is to add time with the method - (void)addTime
one is to subtract time with the method - (void)subtractTime
Those two methods use the property like so:
- (void)addTime
{
CGFloat timeFloat = [self.timeInSeconds floatValue];
// Here I set the value of the property timeInSeconds, but I can't access that value later on in the code!
self.timeInSeconds = [NSNumber numberWithFloat:timeFloat +5];
NSLog(#"Total Time:%#", self.timeInSeconds);
}
The two methods addTime and subtractTime do what they are supposed to do, and they keep a good track of the property timeInSeconds value as I add then subtract then add...
The problem is when I call in the same implementation file the third method which is:
- (void)updateLabelTime
{
self.label.attributedText = [[NSAttributedString alloc]initWithString:[self.timeInSeconds stringValue]];
[self.label setNeedsDisplay];
[NSTimer scheduledTimerWithTimeInterval:0.8 target:self selector:#selector(updateLabelTime) userInfo:nil repeats:YES];
}
I also tried to create a the NSAttributedString with stringWithFormat instead of initWithString but the problem persists which is that instead of returning the value of the property timeInSeconds which i previously set using addTime and subtractTime, it calls the getter which creates a new instance of timeInSeconds since in my getter I have lazy instantiation.
I tried to not write the getter/setter for the property (since I am using iOS 6.1) but it makes no difference.
If I just set the label to some random string, it would work. The problem is that if I know the value of timeInSeconds is 55, it would still create a new _timeInSeconds.
I tried my best with my English since I am French, please don't answer if the question was already asked by a beginner iOS developer and just redirect me. I couldn't find an answer though, thanks!
EDIT: Here is the custom getter
- (float)timeInSeconds
{
if (!_timeInSeconds) {
_timeInSeconds = 0;
}
return _timeInSeconds;
}
SECOND EDIT:
The stupid beginner mistake that I made was that addTime and subtractTime are actually implementing a protocol and they set the property which "lives" in another class which is why I could not access it! That other class that needs the protocol was creating a new instance of the class where addTime and subtractTime is written.
What needs to be done is to set the controller as the delegate for the protocol. I did this in the viewDidLoad method with something like:
self.view.delegate = self;
Thanks for all the help.
In your header file, declare this property:
#property (assign) float timeInSeconds;
In the implementation file:
#synthesize timeInSeconds = _timeInSeconds;
- (void)viewDidLoad
{
[super viewDidLoad];
_timeInSeconds = 0.0f;
}
- (void)addTime
{
_timeInSeconds += 5.0f;
}
This should initialize timeInSeconds to zero and then increment its value by 5 each time you call addTime. To use its value in the label:
- (void)updateLabelTime
{
self.label.text = [NSString stringWithFormat:#"%f", _timeInSeconds];
}
In your custom getter you are assigning a scalar value to an object property. In fact assigning zero to an object property is the equivalent of setting the object to nil.
What you need to do is this:
- (float)timeInSeconds
{
if (!_timeInSeconds) {
_timeInSeconds = [NSNumber numberWithFloat:0.0f];
// or alternatively with the latest version of objective c
// you can more simply use:
// _timeInSeconds = #(0.0f);
}
return _timeInSeconds;
}
I'm newbie to Cocoa/Objective C. I've to change a value of a global NSSTring variable on every iteration of an NSTimer execution. I've declared the variable inside the appdelegate.m at the top of the file so it's global:
NSString *my_string = #"hello";
I call the NSTimer:
[[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(scan:) userInfo:nil repeats:YES] fire];
and inside scan i set the new value to my_string:
- (void) scan:(NSTimer *)timer{
//some execution
my_string = #"the new value";
}
but the variable value is always the same "hello", the content won't change.
Is it possible to do this? Solutions?
You do not need to call fire method, the scheduled timer will fire automatically after the specified interval.
Also, set a breakpoint at the scan: method to find out if it is called.
If you declare your my_string variable in the .m file then other files won't be able to see it (you #import the .h files not the .m). Do you do the timer stuff in the same file (appdelegate.m)?
I recommend not having global variables like this as it will often confuse things as the project builds up. You can have it either as an ivar with an accessor, or as static in the #implementation block with a static accessor so that you can have access to a unique instance from anywhere.
You can log the change to make sure it happens or set a breakpoint.
- (void) scan:(NSTimer *)timer{
//some execution
my_string = #"the new value";
NSLog(#"Changed my_string to %#", my_string);
}
I want my NSTimer to speed up each time it's run:
-(void)runtimer {
int delay = delay - 1;
[NSTimer scheduledTimerWithTimeInterval:(delay)
target:self
selector:#selector(timerTriggered:)
userInfo:nil
repeats:YES];
}
But this doesn't work. How can I make the delay keep getting smaller and smaller?
I needed this myself and wrote a component CPAccelerationTimer (Github):
[[CPAccelerationTimer accelerationTimerWithTicks:20
totalDuration:10.0
controlPoint1:CGPointMake(0.5, 0.0) // ease in
controlPoint2:CGPointMake(1.0, 1.0)
atEachTickDo:^(NSUInteger tickIndex) {
[self timerTriggered:nil];
} completion:^{
[self timerTriggered:nil];
}]
run];
This calls -timerTriggered: 20 times, spread out over 10 seconds, with ever-decreasing delays (as specified by the given Bézier curve).
Every time this method is run, you make a new variable called delay, then try to set it to itself minus 1. This is Undefined Behavior (the variable was not initialized to anything), and is likely to result in a garbage value for delay.*
You need to store the delay in an instance variable.
- (void) runTimer {
// You are declaring a new int called |delay| here.
int delay = delay - 1;
// This is not the same |delay| that you have declared in your header.
// To access that variable, use:
delay = delay - 1;
*A sinus infestation by evil-aligned supernatural beings is also a possibility.
You have to declare the delay somewhere, like in the class interface or as a static variable.
Also, create a new timer every time, instead of having it repeat.
int delay = INITIAL_DELAY;
-(void)runtimer{
[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)(delay--) target:self selector:#selector(runTimer:) userInfo:nil repeats:NO];
}
You cannot change the fire interval of the timer once you have created it. If you want a different interval you must invalidate the previous timer (hence you should keep a reference to it), and create a new timer with a different interval.
my app has a function, it gets a value from a NSTextField and then declare the variable, like this:
- (IBAction)startTimer
//all the other code
int totalTime = secs + hoursInSeconds + minutesInSeconds
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerHandler) userInfo:nil repeats:YES];
then, i want to use the local variable totalTime in another function which processes the NSTimer.
- (void)timerHandler
//all other code
totalTime = totalTime - 1;
//invalidate timer when it reaches 0
if (totalTime == 0.0) {
[timer invalidate];
however, as the variable totalTime is a local variable, i cannot use the value, and i cannot move the code over as NSTimer calls it every 1 sec and as the user may change the variable (and thus redeclaring it).
so, is there any way i can get a local variable from a function and implement the variable in another function which can be changed dynamically? or can i implement a NSTimer countdown by just using one function
You could wrap the value in the timer's userInfo:
NSNumber *totalTimeNumber = [NSNumber numberWithInt:totalTime];
timer = [NSTimer scheduledTimerWithTimeInterval:... target:... selector:... userInfo:totalTimeNumber repeats:...];
Or just make it an instance variable.
Well, here's a fun one that works with local variables, instead of instance variables but only on Mac OS 10.6/iOS 4 and above:
-(IBAction)startTimer:(id)sender
{
// ensure, that the variables we'll capture in the block are mutable
__block int totalTime = ...
__block NSTimer *timer;
void (^timerBlock)() = ^{
if (--totalTime <= 0) { // this comparison is much less fragile...
[timer invalidate];
}
};
// If you'd call timerBlock() at this point you'll crash because timer contains junk!
// However, (since timer is declared as __block) we can give it a meaningful value now and have it updated inside of the block, as well:
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerHandler:) userInfo:timerBlock repeats:YES];
}
-(void)timerHandler:(NSTimer*)timer
{
((void (^)())[timer userInfo])(); // retrieve the block and run it
}
Caveat:
Since I'm sending this from my phone, I am not 100% sure about the cast in timerHandler:. But it's something along this line...
You should be able to omit the cast altogether, but will definitely see a warning then.
I'm having problems starting & stopping NSTimers. The docs say that a timer is stopped by [timer invalidate];
I have a timer object declared as such
.h
NSTimer *incrementTimer;
#property (nonatomic, retain) NSTimer *incrementTimer;
.m
#synthesize incrementTimer;
-(void)dealloc {
[incrementTimer release];
[super dealloc];
}
-The usual.
When it's needed, my method does the following:
-(void)setGenCount {
if(!condition1 && condition2) {
incrementTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector:#selector(incrementBatteryVoltage:)
userInfo: nil
repeats: YES];
}
}
Everything above works fine. However, once that timer does it's job, I want it to invalidate itself. I invalidate the timer because there is an equal decrement method that could be called and would fight against the incrementTimer if it was still active. (Previously, I noticed that my two timers, if active, were acting on the same ivar by increasing & decreasing the value (a sort of fight)... without crashing) The selector called works as follows:
-(void)incrementBatteryVoltage:(NSTimer *)timer {
if(battVoltage < 24.0) {
generatorDisplay.battVoltage += 0.1;
}
if(battery1Voltage == 24.0) {
[timer invalidate];
}
}
I have an equal method that Decrements the battery count. (previously mentioned)
Due to my program design: the interface simulates a voltage display. When the "machine" is turned off, I want all the timers invalidated, regardless of what any voltage value is. I'm doing this by checking to see if the timer is valid.
-(void)deEnergizeDisplays {
if([decrementTimer isValid]) {
[decrementTimer invalidate];
decrementTimer = nil;
}
if([incrementTimer isValid]) {
[incrementTimer invalidate];
incrementTimer = nil;
}
I'm getting numerous "BAD_ACCESS" crashes. The erroneous line call is always pointing toward my [timer isValid] call. It seems that if the timer is invalidated... the pointer
doesn't exist either. I know that the [timer invalidate] message disables the timer and then it is removed from the run loop and then it is released. And my understanding is: it is an autoreleased object per it's naming covention.
My thought are: If I'm sending a retain message, shouldn't the reference still exist? I've tried several combinations, taking away:
timer = nil;
or even instead of:
if([timer isValid])
I tried :
if([timer != nil])
and:
if(timer)
I always get the same crash. Thanks for any help on starting & stopping NSTimers.
UPDATE: See Darren's answer. The problem is that you are not using your property accessor when setting the timers. Instead of:
incrementTimer = [NSTimer ...
You should have:
self.incrementTimer = [NSTimer ...
The self.propertyName = ... syntax will call your accessor method, and thereby automatically retain the object that you send to it (since your property is set up as retain). Simply calling propertyName = ... does not use the property accessor. You are simply changing the value of your ivar directly.
UPDATE #2: After an enlightening conversation with Peter Hosey (see comments), I have removed my earlier suggestion to "never retain or release" your timer object. I have also completely re-written my earlier code because I think the following is a better approach:
Controller.h:
NSTimer *voltageTimer;
float targetBatteryVoltage;
...
#property (nonatomic, retain) NSTimer *voltageTimer;
Controller.m:
#implementation Controller
#synthesize voltageTimer;
- (void)stopVoltageTimer {
[voltageTimer invalidate];
self.voltageTimer = nil;
}
- (void)setTargetBatteryVoltage:(float)target {
[voltageTimer invalidate];
targetBatteryVoltage = target;
self.voltageTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector: #selector(updateBatteryVoltage:)
userInfo: nil
repeats: YES];
}
- (void)updateBatteryVoltage:(NSTimer *)timer {
const float increment = 0.1;
if (abs(battVoltage - targetBatteryVoltage) < increment) {
[timer invalidate];
}
else if (battVoltage < targetBatteryVoltage) {
generatorDisplay.battVoltage += increment;
}
else if (battVoltage > targetBatteryVoltage) {
generatorDisplay.battVoltage -= increment;
}
}
Now, you can simply set a target battery voltage, and the timer magic will happen behind the scenes:
[self setTargetBatteryVoltage:24.0];
Your power-off method would look as follows:
- (void)deEnergizeDisplays {
[self stopVoltageTimer];
}
You need to retain the value assigned to incrementTimer in setGenCount. You can do this automatically by using your synthesized property, which is accessed via self.:
self.incrementTimer = [NSTimer scheduledTimerWithTimeInterval: ...