Timer app supporting both iOS 4.x and iPhone OS 3.x

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.

Preparations

  1. Create View-Based application
  2. Open main xib and make it looked like this

3. The last touch: open Target properties and chane UIKit.framework linking to Weak. It’s required when different versions of framework are used. We’ll use UILocalNotification from 4.x.
You’ll have “dyld: Symbol not found: _OBJC_CLASS_$_UILocalNotification” without it on iPad iOS 3.2, for example.

Interface
Take a look at interface and connect each IBOutlet and IBAction.
@interface TimerAppViewController : UIViewController 
{
IBOutlet UIButton *buttonStart;
IBOutlet UIButton *buttonStop;
}
 
- (IBAction) StartTimer;
- (IBAction) StopTimer;
- (void) EndTimer;
 
- (void) cancelAlarms;
- (void) scheduleAlarmAfterSeconds:(int)seconds;
 
@end
Implementation
This is the most difficult part. Firstly define how it should work in both ways.

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
Modern style:
  • 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

And traditionally (already) there is a lazy version of the article: a video and the project source code.


Be effective! ;)
About these ads
This entry was posted in iOS 4.0, UILocalNotification, Vipassana app. Bookmark the permalink.

2 Responses to Timer app supporting both iOS 4.x and iPhone OS 3.x

  1. Beatriz says:

    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];
    }

  2. Slava says:

    Does that work for you?
    As I remember, I found some exceptions when trying that.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s