I'm new to programming, and I've been stuck for a while on the following:
When I run the attached code, everything seems to work fine, then the progress bar starts counting up again. The console keeps printing the invalidation confirmation string, but the timer seems to keep rolling. I appreciate your help.
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize doneYet;
#synthesize cooking;
#synthesize hidebutton;
- (void)viewDidLoad;
{
[super viewDidLoad];
[doneYet setProgress:doneYet.progress=0.0];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[self setDoneYet:nil];
[self setHidebutton:nil];
[self setCooking:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
-(void)startTimer
{CurrentTimer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFired) userInfo:Nil repeats:YES];}
-(void)timerFired
{ if (doneYet.progress<1) {
[doneYet setProgress:doneYet.progress+0.1];
}
else if (doneYet.progress=1)
{
(void) [CurrentTimer invalidate];
NSLog(#"invalidated CurrentTimer");
hidebutton.hidden = FALSE;
cooking.hidden = TRUE;
[doneYet setProgress:doneYet.progress=0.0];
}
}
- (IBAction)Eggtimer:(id)sender
{
[self startTimer];
hidebutton.hidden = TRUE;
cooking.hidden = FALSE;
;}
#end
This seems a problem
else if (doneYet.progress=1)
for 2 reasons:
the equality comparison operator is == and not = (which is the assign operator)
you are comparing a float number for an exact value, this is never good because floating numbers are not precise, you should check for progress >= 1.0, otherwise you could go over 1.0 (eg 1.0000001) and skip stop condition
In addition in the stop condition you do
[doneYet setProgress:doneYet.progress=0.0];
but formerly it should be
[doneYet setProgress:0.0];
I think you should do this, retain the NSTimer object.
CurrentTimer=[[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFired) userInfo:Nil repeats:YES] retain];
One thing you can do is change your startTimer as
-(void)startTimer
{
[CurrentTimer invalidate];
CurrentTimer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFired) userInfo:Nil repeats:YES];
}
This will invalidate old timer and there will be only one timer.
My error was in designating the button that commands startTimer as the FirstResponder.
Sorry for using your time and advice on something that was the result of an error not in the code.
Thanks again to everyone who offered their advice. It means a lot to a beginner!
Related
I am making a game and I need to know if you can make a if statement that makes it like this:
if object.hidden = YES for 5 seconds{
do these things
}
Could someone please tell me if this is possible, and if so how this would work?
Put a timestamp on your object at the time it was hidden. Then convert your test into a "how long has this object been hidden". Pseudocode (since I don't actually have experience with objective C):
if hidden and elapsed time since hidden > 5 seconds:
do stuff
You can use CACurrentMediaTime(), for an accurate time interval.
sample:
CFTimeInterval start = CACurrentMediaTime();
for(int i=0; i<=10000; i++) {
NSLog(#"%i", i);
}
CFTimeInterval elapsed = CACurrentMediaTime() - start;
NSLog(#"elapsedTime : %f", elapsed);
Make sure you've added QuartzCore framework in your traget's settings
You could subclass Object and build that functionality in. Something like:
#interface ObjectSubclass : Object
#property (nonatomic, strong) NSTimer *timer;
#end
#implementation ObjectSubclass
-(void)setHidden:(BOOL)hidden{
[super setHidden:hidden];
if (hidden){
if (self.timer) {
[self.timer invalidate];
}
self.timer = [NSTimer timerWithTimeInterval:5
target:self
selector:#selector(doStuff:)
userInfo:nil
repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:self.timer
forMode:NSDefaultRunLoopMode];
} else {
[self.timer invalidate];
self.timer = nil;
}
}
I am developing a game for class where a timer is ran when a button is pressed.
-(void)setTimer{
self->mTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(buttonNotPushed) userInfo: nil repeats:NO];
}
-(void)resetTimer{
[self->mTimer invalidate];
self->mTimer = nil;
Here is a snippet of how the timer code is used.
- (IBAction)yellowDot1:(id)sender {
if ([_labelColor.text isEqualToString:#"Yellow"]) {
[ self Scoring];
[self label];
[self resetTimer];
[self setTimer];
}
else ([self GameOver]);
}
- (IBAction)redDot1:(id)sender {
if ([_labelColor.text isEqualToString:#"Red"]) {
[ self Scoring];
[self label];
[self resetTimer];
[self setTimer];
}
else ([self GameOver]);
}
The game presents with a Play button which modals over to the next screen. Currently at 5 seconds, I would like to create a "difficult" mode where at the home screen, the user clicks on a "difficult" button and the timer for the game runs at 2 seconds instead. Right now, I am contemplating duplicating my storyboard and view controllers and going that way where I just make the timer a different interval. Is a shorter way possible through code for a difficult mode?
Simply declare a global a variable for this. Use a class (maybe a singleton) that holds the mode and use a -(float) getGameDuration; method
lets say...
// GameHandler.h
static GameHandler *sharedInstance = nil;
#interface GameHandler : NSObject
{
GameMode *mode;
}
typedef enum{
GAMEMODE_EASY = 0,
GAMEMODE_NORMAL,
GAMEMODE_HARD
} GameMode;
-(float) getGameDuration;
implementation
// GameHandler.m
+ (GameHandler *)sharedInstance // implement singleton
{};
-(float) getGameDuration
{
switch(mode)
{
case GAMEMODE_EASY:
return 10.f;
case GAMEMODE_NORMAL:
return 5.f;
case GAMEMODE_HARD:
return 2.f;
}
return 5.f;
}
// your other classes
float gameDurration = [[GameHandler sharedInstance] getGameDuration];
self->mTimer = [NSTimer scheduledTimerWithTimeInterval:gameDurration
target:self
selector:#selector(buttonNotPushed)
userInfo:nil
repeats:NO];
I am showing a count down timer using UILabel and NSTimer -
-(void)a_Method
{
[coolTimeLbl setNeedsDisplay];
coolTime = 5; // it is an int
coolingTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(cooling) userInfo:nil repeats:YES]; // NSTimer
}
-(void)cooling
{
if (coolTime>0)
{
coolTime = coolTime-1;
NSLog(#" coolTime----%#",coolTime);
coolTimeLbl.text =[NSString stringWithFormat:#"%d",coolTime];
NSLog(#" coolTimeLbl----%#",coolTimeLbl.text);
}
else
{
[coolingTimer invalidate];
coolingTimer = nil;
}
}
The first time everything works fine and I am getting coolTimeLbl.text as - 4 3 2 1 0
But the second time when I call aMethod, coolTimeLbl is not getting updated properly - it is like 3 2 0 etc (some weird behavior)
However both NSLogs (coolTime & coolTimeLbl) print perfectly all the times and values.
Why does this happen? I tried many ways like NSNotification etc.
Please help me to fix this.
If you're calling a_Method more than once before coolingTimer invalidates itself, the timer will tick more than once.
You should add some boolean like ;
BOOL isTimerValid;
in a_Method,
if(!isTimerValid)
{
isTimerValid = YES;
coolingTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(cooling) userInfo:nil repeats:YES]; // NSTimer
}
in cooling,
else
{
isTimerValid = NO;
....
}
Had the same issue in one of my viewControllers and another one was working OK with same NSTimer code. Looked at about 20 SO threads to get it solved. No luck. In my case
myLabel.opaque = false
solved it.
Don't ask me why.
I'm trying to create a simple countdown timer app for myself. So far I've figured out how to create the countdown timers with a stop/reset action on them for a single button I've got attached to the timer.
However, I would like to add multiple timers to the same page and I'm not really sure how to do about making extra calls for the timers. Each of the timers would have it's own number to count down from (7 minutes for one, 3 minutes for the other, etc). These are set intervals that the user is not able to change. Google hasn't really worked out for me on how to do this so I'm hoping someone can at least guide me in the right direction. Below is my code snippets:
ViewController.h
#interface ViewController : UIViewController {
IBOutlet UILabel *firstCountdownLabel;
NSTimer *firstCountdownTimer;
bool timerActive;
int secondsCount;
}
- (IBAction)start:(id)sender;
- (void)timerRun;
#end
ViewController.m
#interface ViewController ()
#end
#implementation ViewController
- (void) timerRun {
secondsCount = secondsCount - 1;
int minutes = secondsCount / 60;
int seconds = secondsCount - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
firstCountdownLabel.text = timerOutput;
if (secondsCount == 0) {
[firstCountdownTimer invalidate];
firstCountdownTimer = nil;
}
}
//- (void) setTimer {
- (IBAction)start:(id)sender {
secondsCount = 420;
if (timerActive == NO) {
timerActive = YES;
self->firstCountdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
else {
timerActive=NO;
[self->firstCountdownTimer invalidate];
self->firstCountdownTimer = nil;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// [self setTimer];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Google doesn't help in showing you how to implement original application ideas.
If you want multiple timers, define multiple timer instance variables:
#interface ViewController : UIViewController
{
IBOutlet UILabel *timer1Label;
IBOutlet UILabel *timer2Label;
IBOutlet UILabel *timer3Label;
NSTimer *timer1;
NSTimer *timer2;
NSTimer *timer3;
int timer1Count;
int timer2Count;
int timer3Count;
bool timer1Active;
bool timer2Active;
bool timer3Active;
}
Then create a separate IBAction for each button that starts each of the timers:
- (IBAction)startTimer1:(id)sender
{
timer1Count = 420;
if (timer1Active == NO)
{
timer1Active = YES;
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timer1Run:)
userInfo:nil
repeats:YES];
}
else
{
timer1Active=NO;
[timer1 invalidate];
timer1 = nil;
}
}
- (void) timer1Run: (NSTimer*) timer
{
timer1Count -= 1;
int minutes = timer1Count / 60;
int seconds = timer1Count - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
timer1Label = timerOutput;
if (timer1Count == 0) {
[timer2 invalidate];
timer2 = nil;
}
}
Duplicate the above code for each timer, using "timer2" and "timer3" in place of "timer1". Change the time counts for each one to the desired values. (I changed the names from "firstTimer" to "timer1" because it's easier to edit the code to support multiple timers that way.
I did not write 3 versions of each method for you because you need to figure this out rather than copy & pasting in code that you don't understand.
It would be possible, and require less code, to use the same IBAction method for all your start timer buttons, and have the code check the tag on the button to decide which timer to start.
The code might look like this:
- (IBAction)startTimer1:(id)sender
{
int tag = [sender tag];
switch (tag)
{
case 1: //timer 1
//Put code to start timer 1 here
break;
case 2: //timer 2
//put code to start timer 2 here
break;
}
}
But that might be a bit over your head at the moment.
By the way, forget you ever saw the "self->variable" syntax. it is slower and more error-prone than just referring to the instance variable directly. using object->variable syntax also allows you to access the instance variables of other objects, which is bad practice. You should always use properties to access the instance variables of objects other than yourself.
Also, the timer method should take a single parameter, a timer. I corrected the timer method in the above code.
Create a class as YourTimer with few properties like
NSString *timerLabel;
NSTimer *timer;
NSInteger timerCounter;
Now create an array of YourTimer objects. Then you can access it easily.
This will be modular, maintainable and reusable code, as may be later on you need one more identifier to be with all timers, hence wrap them in one class and use it.
- (IBAction) goStrobe:(id) sender {
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(runLoop:)
userInfo:nil
repeats:YES];
}
- (void) runLoop {
if (imageTwo.hidden = YES) {
imageTwo.hidden = NO;
}
if (imageTwo.hidden = NO) {
imageTwo.hidden = YES;
}
}
My code is above. Every time I trigger goStrobe, it crashes and I can't figure out why. Any help would be greatly appreciated. Thanks
It's your runLoop function signature which is wrong in the selector, just remove the ":" at the end. You don't need this because your function does not take any parameters.