I am making a stopwatch, but I only have the start button working. When the start button is pressed it enters a loop:
- (void)stopwatch
{
NSInteger hourInt = [hourLabel.text intValue];
NSInteger minuteInt = [minuteLabel.text intValue];
NSInteger secondInt = [secondLabel.text intValue];
if (secondInt == 59) {
secondInt = 0;
if (minuteInt == 59) {
minuteInt = 0;
if (hourInt == 23) {
hourInt = 0;
} else {
hourInt += 1;
}
} else {
minuteInt += 1;
}
} else {
secondInt += 1;
}
NSString *hourString = [NSString stringWithFormat:#"%02d", hourInt];
NSString *minuteString = [NSString stringWithFormat:#"%02d", minuteInt];
NSString *secondString = [NSString stringWithFormat:#"%02d", secondInt];
hourLabel.text = hourString;
minuteLabel.text = minuteString;
secondLabel.text = secondString;
CGRect hourFrame = self->hourBar.frame;
CGRect minuteFrame = self->minuteBar.frame;
CGRect secondFrame = self->secondBar.frame;
if ((NSInteger)hourFrame.size.height != hourInt) { // check if we need to modify
hourFrame.origin.y -= ((hourInt * 10.0) - hourFrame.size.height);
hourFrame.size.height = (hourInt * 10.0);
self->hourBar.frame = hourFrame;
}
if ((NSInteger)minuteFrame.size.height != minuteInt) { // check if we need to modify
minuteFrame.origin.y -= ((minuteInt * 4.0) - minuteFrame.size.height);
minuteFrame.size.height = (minuteInt * 4.0);
self->minuteBar.frame = minuteFrame;
}
if ((NSInteger)secondFrame.size.height != secondInt) { // check if we need to modify
secondFrame.origin.y -= ((secondInt * 4.0) - secondFrame.size.height);
secondFrame.size.height = (secondInt * 4.0);
self->secondBar.frame = secondFrame;
}
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(stopwatch) userInfo:nil repeats:NO];
}
When the stop button is pressed, I want this loop to be paused so that when the user presses start, it resumes the stopwatch.
When the reset button is pressed, I want the loop to stop and reset back to 0.
If you could make your answer as simple as possible, that would be really great because I'm only a beginner!
You should take the timer out of your 'loop' method and make it repeating. It should be the thing that is driving the stopwatch, not that fact that you have started your 'loop'. Then, you can stop the timer when you need to and restart it later. You can also google to find out the correct way to pause the timer (you need to change the timer to start at a specified fire date to be 100% accurate, but just knowing how many seconds are left may be enough for your case).
Based on your previous question it sounds like you have moved your timer outside of your stopwatch method. Remember, timers and loops are two very different things.
You can stop an NSTimer by calling invalidate on it. Please read Apple's documentation for more details.
One way that might not be completely thread safe but I just tested it and it works is to have a variable that is set to true when you click start and false when you click stop.
For example, in my .h, I added #property (nonatomic) BOOL go;, then as the first line of the method you provided, add an if check that looks like this:
- (void)stopwatch {
if (!self.go) return;
//..do the rest of your stopwatch code
//the timer call
}
then my start and stop button calls look like:
- (void)start {
if (!self.go) { // Do not call the stopwatch method again if it is already going.
self.go = YES;
[self stopwatch];
}
}
- (void)stop {
self.go = NO;
}
Loop is the wrong construct for this type of operation. For better control, use NSTimer.
A relevant example can be found here.
EDIT:
My above answer was based on prior version of your question.
So yes, the NSTimer should be the controlling thing for your loop, not part of the loop.
General implementation:
Start should only mark timer starting. Also note the current time from system clock. (this is optional though)
Timer function should change value of the time variable, and compare it against the stopwatch set value. If time elapsed == set value, stop the timer using invalidate.
Stop should interrupt the timer and stop it, and also the elapsed time value should be set to 0. Time Variable should be reset too, if you do not want to reuse this same time value again.
Related
I have researched online and couldn't find an answer. On Xcode 6 I have a error:
Semantic issue: Comparison between pointer and integer ('UILabel*' and 'int')
The code is (ViewController.m):
-(void)enemyMovement {
enemyShip.center = CGPointMake(enemyShip.center.x, enemyShip.center.y + 2);
if (CGRectIntersectsRect(enemyShip.frame, mothersShip.frame)) {
lives = lives - 1;
liveString = [NSString stringWithFormat:#"LIVES: %i", lives];
livesLabel.text = liveString;
//Stop enemy moving
[enemyMovementTimer invalidate];
if (livesLabel > 0) {
[self positionEnemy];
}
if ((livesLabel = 0)) {
[self gameOver];
}
}
}
-(void)gameOver {
[enemyMovementTimer invalidate];
[missleMovementTimer invalidate];
[self performSelector:#selector(replayGame) withObject:nil afterDelay:3];
}
- (void)replayGame {
score = 0;
lives = 3;
scoreString = [NSString stringWithFormat:#"SCORE: 0"];
liveString = [NSString stringWithFormat:#"LIVES: 3"];
scoreLabel.text = scoreString;
livesLabel.text = liveString;
startButton.hidden = YES;
friendlyShip.hidden = NO;
enemyShip.hidden = NO;
mothersShip.hidden = NO;
scoreLabel.hidden = NO;
livesLabel.hidden = NO;
[self positionEnemy];
}
The error occurs when integer is '1' but when integer is '0' the error doesn't occur and the lives goes into negative numbers, but the command doesn't occur for either number. The command is to revert back to the start of the game. I Think the problem is something to do with the way that the lives label is set out, I also think it might be something about the '=='. But i am not sure completely I have tried to solve this problem, but I have been unsuccessful, several times. I hope you can help me.
Thank You in advance
livesLabel > 0 is comparing the memory address of the livesLabel object to the number 0. What you want is to compare 0 to the text that the label shows. The comparison would look like this: [livesLabel.text intValue] > 0
You're comparing a label to a number. You might need to relearn some fundamental programming concepts, and I can't help you there, but a UILabel has a text property which can be parsed into an int with intValue.
if (livesLabel > 0) {
[self positionEnemy];
}
if ((livesLabel = 0)) {
[self gameOver];
}
I can't even tell what you're trying to achieve, you should compare lives instead, not the label to a number, its like trying to compare a sticker with a number, it makes absolutely no sense.
I am working on an iOS 7+ app and would like to animated change the content of an UILabel. I do not want do do any graphical animation like fade out old content / fade in new content. Thus all the standard animation features iOS offers like Layer animations or animation blocks cannot be uses (at least I think so).
Assume the UILabel shows some meter values like "200 V" and this text should be changed to "400 V". The text should not just jump from "200 V" to "400 V" but should be counted up using some easing function: "200 V", "220 V", "240 V"... "390 V", "395 V" ... "400 V"
In Android could easily be solved using a ValueAnimator:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setInterpolation(new EaseOutInterpolator());
animation.setDuration(2500);
animation.setStartDelay(500);
animation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpate(ValueAnimator animator) {
float currentValue = animator.getAnimatedValue.floatValue();
label1.setText(String.format("%.2", fromValue1 + ((toValue1 - fromValue1) * currentValue)));
label2.setText(String.format("%.2", fromValue2 + ((toValue2 - fromValue2) * currentValue)));
...
}
});
animation.start();
Is there such thing in iOS as well? I found different solution for this but they are all pretty old (2010/11) and all end up implementing this behavior manually using NSTimer and own easing functions.
It is out of question that one can implement this on his own, but this would be quite cumbersome and not very elegant. So: Is there something build in iOS to solve this or are there at least convenient third party implementation available?
Thank you very much!
Since I found no tailored solution for this I created my own: A simple Animator Class which handles the Easing:
// MyValueAnimation.h
typedef void (^MyAnimationBlock)(double animationValue);
#interface MyValueAnimation : NSObject
- (void)startAnimation:(MyAnimationBlock)animationBlock runtime:(NSUInteger)runtime delay:(NSUInteger)delay;
#end
// MyValueAnimation.m
#import "MyValueAnimation.h"
// Number of seconds between each animation step
#define kStepSize 0.05
#interface MyValueAnimation () {
NSTimer *timer;
NSUInteger totalRunTime; // Total duration of the animation (delay not included)
NSUInteger currentRuntime; // Time the animation is already running
MyAnimationBlock animationBlock;
}
#end
#implementation MyValueAnimation
- (void)startAnimation:(MyAnimationBlock)block runtime:(NSUInteger)runtime delay:(NSUInteger)delay {
if (timer != nil)
[timer invalidate];
timer = nil;
totalRunTime = runtime;
animationBlock = block;
currentRuntime = 0;
if (block != nil) {
if (delay > 0) {
// Wait to delay the start. Convert delay from millis to seconds
double delaySeconds = (double)delay / 1000.0;
timer = [NSTimer scheduledTimerWithTimeInterval:delaySeconds target:self selector:#selector(delayTick:) userInfo:nil repeats:false];
} else {
// Run the animation
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:#selector(animationTick:) userInfo:nil repeats:true];
}
}
}
- (void)delayTick:(NSTimer *)delayTimer {
// End of delay -> run animation
[delayTimer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:#selector(animationTick:) userInfo:nil repeats:true];
}
- (void)animationTick:(NSTimer *)animationTimer {
NSUInteger step = 1000 * kStepSize; // step size/length in milli seconds
currentRuntime += step;
double progress = MIN((double)currentRuntime / (double)totalRunTime, 1.0);
// Progress is a value between 0 and 1. The easing function maps this
// to the animationValue which is than used inside the animationBlock
// to calculate the current value of the animiation
double animationValue = [self customEaseOut:progress];
if (animationBlock != nil)
animationBlock(animationValue);
if (progress >= 1.0) {
// Animation complete
[timer invalidate];
timer = nil;
}
}
- (double)customEaseOut:(double)t {
// Use any easing function you like to animate your values...
// http://rechneronline.de/function-graphs/
// http://sol.gfxile.net/interpolation/
return (1 - pow(1-t, 2));
}
#end
// =============================================================
// Some code using the animation
- (void)animateValueFrom:(double)fromValue to:(double)toValue {
if (valueAnimation == nil)
valueAnimation = [[MyValueAnimation alloc] init];
MyAnimationBlock animationBlock = ^(double animationValue) {
double currentValue = fromValue + ((toValue - fromValue) * animationValue);
someLabel.text = [NSString stringWithFormat:"%dV", currentValue];
};
[valueAnimation startAnimation:animationBlock runtime:1500 delay:500];
}
Maybe not the prettiest solution but it works :-)
I know a solution but its for fast iteration because final iterations can jump through value (looks not beautifully if there is a slow search), but the decision simple and short, can not the most beautiful from a architecture(but it can be corrected if it is necessary) realisation.
- (void)someAction
{
[self animateValue:0 toValue:1111 withStep:7 andIntervalSpeed:5];
}
-(void)animateValue:(int)value toValue:(int)toValue withStep:(int)step andIntervalSpeed:(int64_t)intervalSpeed
{
self.currentValue = value; // #property (nonatomic) int currentValue;
NSUInteger numberofIteration = (toValue - value)/step;
int64_t interval = 0.0;
for (NSUInteger i = 0; i < numberofIteration; i++) {
dispatch_time_t start = DISPATCH_TIME_NOW;
interval += intervalSpeed;
dispatch_after(dispatch_time(start, interval * USEC_PER_SEC), dispatch_get_main_queue(), ^{
if (((toValue - value)%step != 0) && (i == (numberofIteration-1)))
{
self.currentValue = toValue;
}
else
{
self.currentValue+= step;
}
NSLog(#"%d",self.currentValue);
self.someLabel.text = [NSString stringWithFormat:#"%d",self.currentValue];
});
}
}
I am writing a basic hh:mm:ss timer app. I have the timer functioning perfectly with start, pause and stop options. My problem is when I swap screens on the app the timer stops and returns to 00:00:00.
Do I need to save it in an array that updates no matter what? Is there a method I can implement or a tutorial available to have the timer continuously count even if I swap screens, go to another app or the screen sleeps?
My code for anyone who wants to implement a timer:
//================= START WORK TIMER =======================//
//Work Timer
- (void)updateWorkTimer {
if(working == false) return;
//calculate elapsed time
NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsed = workSecondsAlreadyRun + currentTime - startWorkTime;
// extract out the minutes, seconds, and hours of seconds from elapsed time:
int hours = (int)(mins / 60.0);
elapsed -= hours * 60;
int workingMins = (int)(elapsed / 60.0);
elapsed -= workingMins * 60;
int secs = (int) (elapsed);
//update our label using the format of 00:00:00
self.workTimerLabel.text = [NSString stringWithFormat:#"%02u:%02u:%02u", hours, workingMins, secs];
//call updateTime every 1 second
[self performSelector:#selector(updateWorkTimer) withObject:self afterDelay:1];
}
//=================================================================================================================
- (IBAction)startWorkButton:(id)sender {
if(working == false) {
//start timer
working = true;
startWorkDate = [[NSDate alloc] init];
startWorkTime = [NSDate timeIntervalSinceReferenceDate];
//Set color and title for the button
[self.startWorkButton setBackgroundColor:[UIColor blueColor]];
[sender setTitle:#"Pause Work" forState:UIControlStateNormal];
[self updateWorkTimer];
}
else {
//pause timer
workSecondsAlreadyRun += [[NSDate date] timeIntervalSinceDate:startWorkDate];
startWorkDate = [[NSDate alloc] init];
//Set color and title for the button
UIColor * color = [UIColor colorWithRed:56/255.0f green:171/255.0f blue:10/255.0f alpha:1.0f];
[self.startWorkButton setBackgroundColor: color ];
[sender setTitle:#"Resume Work" forState:UIControlStateNormal];
working = false;
}
}
Thanks in advance.
You don't need to have the timer running the whole time. Instead, you can simply calculate the time interval (the time that has passed since your app went to background) once your app opens again and then add this to the value of the timer.
For this you need to store the current value of the timer and the current time ([NSDate date]) with NSUserDefaults when the app goes to background, and use this data to calculate what value the timer should have once your app starts again. To intercept going to background and foreground you should subscribe to UIApplicationDidEnterBackgroundNotification and UIApplicationDidBecomeActiveNotification, because viewDidAppear and viewDidDisappear won't get called.
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.
I just want to run a certain method for 20 seconds when user presses start button and stop it by it's own rather than using a button to do it. But it should also trigger when ever the user press start button after 20 seconds in next turn.
How can i use NStimer to implement this?
Sorry for not posting any codes
Thanks in advance!
// Assume ARC - otherwise do the proper retains/releases
{
NSTimer *timer; // keep a reference around so you can cancel the timer if you need to
NSDate *timerDate;
}
- (IBAction)buttonPressed:(id)sender
{
// disable button til the time has passed
[sender setEnabled:NO];
// make sure its diabled til we're done here
timerDate = [NSDate date];
NSTimeInterval interval = 1; // 1 gets you a callback every second. If you just wnat to wait 20 seconds change it
// schedule it
timer = [NSTimer ]scheduledTimerWithTimeInterval:time target:self selector:#selector(myTimeRoutine:) userInfo:nil repeats:YES];
}
- (void)myTimeRoutine:(NSTimer *)timer
{
// do something ...
NSTimeInterval interval = -[timerDate timeIntervalSinceNow]; // return value will be negative, so change sign
if(interval >= 20) {
// I'm done now
myButton.enabled = YES; // so it can be tapped again
[timer cancel]; // dont want to get any more messages
timer = nil; // ARC way to release it
timerDate = nil;
}
}