Updating Vipassana app with retina images and iOS 4 support I almost forget about 3.x users. Despite that months were left from iOS 4.0 release we must tranditionally support previous versions for a while. Especially if taking into account iPhone 3G perfomance on iOS 4.0.x, and generally keep silent about rarity iPhone GSM. I know several guys who still use it and love it by the way.
Now I want to show how to organize simple Timer application with both iOS 4 and iPhone OS support.
- Create View-Based application
- Open main xib and make it looked like this
@interface TimerAppViewController : UIViewController
{
IBOutlet UIButton *buttonStart;
IBOutlet UIButton *buttonStop;
}
- (IBAction) StartTimer;
- (IBAction) StopTimer;
- (void) EndTimer;
- (void) cancelAlarms;
- (void) scheduleAlarmAfterSeconds:(int)seconds;
@end
Oldschool:
- user launches the app, makes some settings and presses to start a timer
- then he has to keep the app working, because no background in iPhone OS 3.x.x
- active display eates bettery a lot, so it’s better to turn display off. There is only one thing we could do via public API – turn on display dimming sensor and offer to user to put a device face down. It works this way, verified.
- Timer is standard NSTimer’s method perform
- when timer is fired then alarm notifies user
- user launches an app, makes the same settings and starts the timer.
- then he may exit the app as well as keep using in old way (keeps working and kills a battery)
- Timer is implemented as Local Notification
- when timer is fired then alarm appears, but:
- if the app is inactive then user has a standard local notification (like a PUSH message)
- if the app is active we should catch Local Notification in AppDelegate and notify user
A little confused, but rather simple in practice.
Schedule local notification (modern style) or NSTimer method perfom (oldschool):
- (void) scheduleAlarmAfterSeconds:(int)seconds
{
[self cancelAlarms];
if (NSClassFromString(@"UILocalNotification"))
{
UILocalNotification* alarm = [[[UILocalNotification alloc] init] autorelease];
if (alarm)
{
alarm.fireDate = [NSDate dateWithTimeIntervalSinceNow:seconds];
alarm.timeZone = [NSTimeZone defaultTimeZone];
alarm.repeatInterval = 0;
alarm.alertBody = @"Time is up!";
[[UIApplication sharedApplication] scheduleLocalNotification:alarm];
}
}
else
{
// schedule timers
[self performSelector:@selector(EndTimer) withObject:nil afterDelay:seconds];
}
}
- (void) cancelAlarms
{
if (NSClassFromString(@"UILocalNotification"))
{
UIApplication *app = [UIApplication sharedApplication];
NSArray *oldNotifications = [app scheduledLocalNotifications];
if ([oldNotifications count] > 0)
[app cancelAllLocalNotifications];
}
else
{
// unschedule timers
[NSTimer cancelPreviousPerformRequestsWithTarget:self selector:@selector(EndTimer) object:nil];
}
}
UI methods to start and to stop the timer, and EndTimer is called when the timer fires.
- (IBAction) StartTimer
{
[self scheduleAlarmAfterSeconds:3];
[[UIApplication sharedApplication] setIdleTimerDisabled:YES]; // disable autolocking
[[UIDevice currentDevice] setProximityMonitoringEnabled:YES]; // enable screen dimming
buttonStart.enabled = NO;
buttonStop.enabled = YES;
}
- (IBAction) StopTimer
{
[[UIDevice currentDevice] setProximityMonitoringEnabled:NO];
[self cancelAlarms];
buttonStart.enabled = YES;
buttonStop.enabled = NO;
}
- (void) EndTimer
{
[self StopTimer];
[self showMessage];
}
Alert notification to user:
- (void) showMessage:(NSString*)message
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:message message:nil delegate:nil cancelButtonTitle:@"ok" otherButtonTitles:nil];
[alert show];
[alert release];
}
- (void) showMessage
{
[self showMessage:@"Time is up!"];
}
In the AppDelegate we should catch a local notification and distinguish if the app is launched from background. If we catch local notification from background then user has pressed the “View” button from local notification popup message, so no additional alarm is required. Otherway the app was active and we should notify user directly calling “EndTimer“.
- (void)applicationDidEnterBackground:(UIApplication *)application {
/*
Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
*/
didEnterBackground = YES;
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
/*
Called as part of transition from the background to the inactive state: here you can undo many of the changes made on entering the background.
*/
wasEnterForeground = YES;
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
if (wasEnterForeground || didEnterBackground)
{
wasEnterForeground = NO;
didEnterBackground = NO;
}
else
{
[viewController EndTimer];
didEnterBackground = NO;
}
}
Demo and Sources



Wouldn't it be easier to do this and forego all the “didEnterBackground” stuff?
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
if (application.applicationState == UIApplicationStateActive) {
[viewController EndTimer];
}
Does that work for you?
As I remember, I found some exceptions when trying that.