patternMinor
Simple 3…2…1 countdown in iOS app
Viewed 0 times
countdownsimpleappios
Problem
I would like to display a 3...2...1 countdown on my iOS app (before to take a picture) but I didn't find an elegant solution. I don't want to make a Countdown app with an NSTimer. For me the important is to keep all code in the same method, use a system like Block.
Do you have a better solution? t works okay, but it's not really elegant link up
self.infoLabel.text = [NSString stringWithFormat:@"3"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.infoLabel.text = [NSString stringWithFormat:@"2"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.infoLabel.text = [NSString stringWithFormat:@"1"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.infoLabel.hidden = YES;
// DO WHAT I WANT AFTER 3 SECs
});
});
});
});Do you have a better solution? t works okay, but it's not really elegant link up
dispatch_after like I did.Solution
The problem with any sort of timer in which each "tick" is a measurement from the previous "tick" is that in software, these "ticks" are not guaranteed to be exact. This means that if your first tick is off by a tenth of a second, every remaining tick will be off by that amount or more. Your second tick is scheduled based on your first tick. This means the actual amount of total time passed is in exact, and the more ticks you have, the more error prone your total time is going to be.
So, for a start, I want to show you what a countdown object class might look like. Then I'll walk through how we'd use this.
CRCountdown.h
CRCountdown.m
Now, this will guarantee that our completion event happens at as accurate a time as possible. We still can't guarantee that it will happen precisely at a specific moment, but the margin of error is drastically reduced by eliminating the possibility of compounding the error for each one of our "update ticks".
Instead, we set up a repeating
It's not even necessary to pass anything for the
But we can go one step further with our
First, in our header, let's add another block typedef:
We also need to change our
This means adding two more properties in our
We need to modify our
And add an update method to handle the update ticks:
The final step is modifying our
Now, using this object is as simple as this:
```
CRCountdownUpdate update = ^(NSUInteger ticks) {
self.infoLabel.text = [NSString stringWi
So, for a start, I want to show you what a countdown object class might look like. Then I'll walk through how we'd use this.
CRCountdown.h
typedef void (^CRCountdownCompletion)(void);
@interface CRCountdown : NSObject
- (void)startCountdownWithInterval:(NSTimeInterval)interval
ticks:(NSUInteger)ticks
completion:(CRCountdownCompletion)completion;
@property (readonly) NSUInteger ticksRemaining;
@property (readonly) NSTimeInterval interval;
@endCRCountdown.m
@interface CRCountdown()
@property NSTimer *timer;
@property (readwrite) NSTimeInterval interval;
@property (copy) CRCountdownCompletion completion;
@end
@implementation CRCountdown
- (void)startCountdownWithInterval:(NSTimeInterval)interval ticks:(NSUInteger)ticks completion:(CRCountdownCompletion)completion {
self.completion = completion;
self.interval = interval;
self.timer = [NSTimer scheduledTimerWithTimeInterval:(interval * ticks) target:self selector:@selector(countdownComplete:) userInfo:nil repeats:NO];
}
- (void)stopCountdown {
[self.timer invalidate];
}
- (NSUInteger)ticksRemaining {
if (self.timer.isValid) {
NSTimeInterval timeRemaining = [self.timer.fireDate timeIntervalSinceDate:[NSDate date]];
return timeRemaining / self.interval;
} else {
return 0;
}
}
- (void)countdownComplete:(NSTimer *)timer {
if (self.completion) {
self.completion();
}
}
@endNow, this will guarantee that our completion event happens at as accurate a time as possible. We still can't guarantee that it will happen precisely at a specific moment, but the margin of error is drastically reduced by eliminating the possibility of compounding the error for each one of our "update ticks".
Instead, we set up a repeating
NSTimer that's in charge of calling some code to update the UI. This UI update code can look at ticksRemaining (and if necessary, multiply this by interval) to determine what it should update the UI to represent.It's not even necessary to pass anything for the
completion argument, as our interested object will already be polling ticksRemaining, so whenever this returns 0, we know that the timer is complete (complete-ish anyway).But we can go one step further with our
Countdown object and provide a means for including an update block that is fired on each tick, if we want. For this, however, we're going to want two NSTimer's ticking away.First, in our header, let's add another block typedef:
typedef void (^CRCountdownUpdate)(NSUInteger);We also need to change our
startCountdown... method to take an argument of this type:- (void)startCountdownWithInterval:(NSTimerInterval)interval
ticks:(NSUInteger)ticks
update:(CRCountdownUpdate)update
completion:(CRCountdownCompletion)completion;This means adding two more properties in our
.m, an updateTimer (NSTimer *) and a CRCountdownUpdate:@property NSTimer *updateTimer;
@property (copy) CRCountdownUpdate update;We need to modify our
countdownComplete: method so it will invalidate the updateTimer (so update never fires after completion):- (void)countdownComplete:(NSTimer *)timer {
[self.updateTimer invalidate];
if (self.completion) {
self.completion();
}
}And add an update method to handle the update ticks:
- (void)countdownUpdate:(NSTimer *)timer {
if (self.update) {
self.update(self.ticksRemaining);
}
}The final step is modifying our
startCountdown... method to match how we updated the header and to take care of setting up the update timer:- (void)startCountdownWithInterval:(NSTimeInterval)interval ticks:(NSUInteger)ticks completion:(CRCountdownCompletion)completion update:(CRCountdownUpdate)update {
self.completion = completion;
self.update = update;
self.interval = interval;
self.timer = [NSTimer scheduledTimerWithTimeInterval:(interval * ticks) target:self selector:@selector(countdownComplete:) userInfo:nil repeats:NO];
if (self.update) {
self.updateTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(countdownUpdate:) userInfo:nil repeats:YES];
}
}Now, using this object is as simple as this:
```
CRCountdownUpdate update = ^(NSUInteger ticks) {
self.infoLabel.text = [NSString stringWi
Code Snippets
typedef void (^CRCountdownCompletion)(void);
@interface CRCountdown : NSObject
- (void)startCountdownWithInterval:(NSTimeInterval)interval
ticks:(NSUInteger)ticks
completion:(CRCountdownCompletion)completion;
@property (readonly) NSUInteger ticksRemaining;
@property (readonly) NSTimeInterval interval;
@end@interface CRCountdown()
@property NSTimer *timer;
@property (readwrite) NSTimeInterval interval;
@property (copy) CRCountdownCompletion completion;
@end
@implementation CRCountdown
- (void)startCountdownWithInterval:(NSTimeInterval)interval ticks:(NSUInteger)ticks completion:(CRCountdownCompletion)completion {
self.completion = completion;
self.interval = interval;
self.timer = [NSTimer scheduledTimerWithTimeInterval:(interval * ticks) target:self selector:@selector(countdownComplete:) userInfo:nil repeats:NO];
}
- (void)stopCountdown {
[self.timer invalidate];
}
- (NSUInteger)ticksRemaining {
if (self.timer.isValid) {
NSTimeInterval timeRemaining = [self.timer.fireDate timeIntervalSinceDate:[NSDate date]];
return timeRemaining / self.interval;
} else {
return 0;
}
}
- (void)countdownComplete:(NSTimer *)timer {
if (self.completion) {
self.completion();
}
}
@endtypedef void (^CRCountdownUpdate)(NSUInteger);- (void)startCountdownWithInterval:(NSTimerInterval)interval
ticks:(NSUInteger)ticks
update:(CRCountdownUpdate)update
completion:(CRCountdownCompletion)completion;@property NSTimer *updateTimer;
@property (copy) CRCountdownUpdate update;Context
StackExchange Code Review Q#87169, answer score: 8
Revisions (0)
No revisions yet.