Search
Follow
Recent Comments
« Application Icon Troubles | Main | Filtering arrays with NSPredicate »
Saturday
Jul312010

Adding Local Notifications with iOS 4

The Apple Push Notification Service (APNS) was released way back in iOS 3.0. That was only just over a year ago but given the pace of development in the mobile OS market that now seems like ancient history. APNS was a first tentative attempt by Apple to address the demand for background processes. It allows client applications to receive updates from a server without having to be running. Apple takes care of pushing the notification to the device and the user can then decide whether to launch the application or not.

Local Notifications

The big problem with APNS is that it requires you to run a server somewhere that interacts with APNS to push a notification to a device. For applications that already have a client-server architecture such as an IM client this is no big deal. There are also a number of third parties who can provide a cost efficient service for small scale users who do not want the trouble of running their own server. However for many applications this is still way too complicated.

The release of iOS 4 brought a number of new features aimed at adding multitasking support one of which was local notifications. I am not sure if you can really say that local notifications count towards multitasking but when compared to APNS they are much simpler to use. There is no central service in fact you do not even need an internet connection to schedule and deliver notifications to an application.

The Example App - RemindMe

To provide a working example of how to add local notifications to an iOS 4 app I have a created a very simple sample application. The user interface is very simple and will not win any design awards. The basic idea is that you enter a message, set the date time and then click the “Set” button to schedule a reminder. At the designated time a message pops up on the screen to remind you.

I will spare you the basic setup of the app, you can download the code and look at that yourself if you are interested. What I want to focus on is the creation of the local notifications and the way the app interacts with iOS when they are delivered.

Creating a Local Notification

The set button is wired up to run a method called scheduleNotification in the view controller which uses the UILocalNotification class to schedule a notification. The code looks as follows:

- (void)scheduleNotification {
	
    [reminderText resignFirstResponder];
    [[UIApplication sharedApplication] cancelAllLocalNotifications];

    Class cls = NSClassFromString(@"UILocalNotification");
    if (cls != nil) {
		
        UILocalNotification *notif = [[cls alloc] init];
        notif.fireDate = [datePicker date];
        notif.timeZone = [NSTimeZone defaultTimeZone];
		
        notif.alertBody = @"Did you forget something?";
        notif.alertAction = @"Show me";
        notif.soundName = UILocalNotificationDefaultSoundName;
        notif.applicationIconBadgeNumber = 1;
		
        NSDictionary *userDict = [NSDictionary dictionaryWithObject:reminderText.text 
                                                forKey:kRemindMeNotificationDataKey];
        notif.userInfo = userDict;
		
        [[UIApplication sharedApplication] scheduleLocalNotification:notif];
        [notif release];
    }
}

I will walk through the code a few lines at a time highlighting the key points:

Cancel existing notifications

    [reminderText resignFirstResponder];
    [[UIApplication sharedApplication] cancelAllLocalNotifications];

The first couple of lines remove the onscreen keyboard if it is being displayed for the text field (reminderText) and also cancel any existing local notifications. For this app we will only allow a single outstanding notification but iOS 4 will allow you to schedule multiple notifications.

Ensuring backwards compatibility

    Class cls = NSClassFromString(@"UILocalNotification");
    if (cls != nil) {

        UILocalNotification *notif = [[cls alloc] init];

As an example of how to allow the code to compile and run on iOS 3.x I have protected the allocation of the UILocalNotification object using NSClassFromString. If you only need to support iOS 4 you can avoid this and allocate the object directly:

        UILocalNotification *notif = [[UILocalNotification alloc] init];

Setting the fire date and time

        notif.fireDate = [datePicker date];
        notif.timeZone = [NSTimeZone defaultTimeZone];

The fire date is the date and time when the system should deliver the notification. For our example app we just use the NSDate object returned by the date picker. If you specify a date that is in the past (or nil) the notification is delivered immediately. The fire date is evaluated using the timeZone property of the UILocalNotification object. If you do not set the timezone the fire data is considered to be GMT time. Generally for alarm clock style reminders you want to use the local time zone when you create the notification. This means that if you set a fire date of 09:00 and then move to a different time zone the alarm will still fire at 09:00 in the new time zone.

Controlling the alert

        notif.alertBody = @"Did you forget something?";
        notif.alertAction = @"Show me";

You control the alert message displayed when the notification is delivered by setting the alertBody and alertAction properties. If your app supports multiple languages you should of course make these localized strings. The alertBody sets the actual message text displayed in the alert, the alertAction property sets the label of the alert button:

One other nice touch is that the alertAction text is also shown as part of the unlock slider if the device is locked when the notification is delivered. The default “slide to unlock” message is changed so that the alertAction text replaces the word unlock. The system also takes care of changing the case of the initial letter so in our example we end up with “slide to show…”.

If you do not want to show the alert action button you can disable it by setting the hasAction property to NO. Assuming you have set alertBody this will still cause the alert to be shown with a single OK button. Note that without the action button there is no option for the user to launch the application from the alert. (If you do not want to see the alert at all you should set alertBody to nil.)

Setting the alert sound

        notif.soundName = UILocalNotificationDefaultSoundName;

You can choose to play a sound when the alert is displayed. The sound file must be part of the application bundle and cannot be more than 30 seconds long. In this case we just use the default system sound. Apple guidelines state that you should only play a sound when also displaying an alert or changing the application icon badge. This makes sense if you think how confusing it would be for the user hearing the device play a sound with no other indication as to which application is the target of the notification.

Setting the application badge

        notif.applicationIconBadgeNumber = 1;

In addition to showing an alert and playing a sound when the application is delivered you can also set the application icon badge. If you want to increment the existing badge number you can get the current value from the application instance [[UIApplication sharedApplication] applicationIconBadgeNumber].

Sending custom user data

        NSDictionary *userDict = [NSDictionary dictionaryWithObject:reminderText.text
                                               forKey:kRemindMeNotificationDataKey];
        notif.userInfo = userDict;

If you need to pass some custom data you can add it a dictionary that is included with the notification. In this example we use it to send the reminder text that the user entered into the text field. We will retrieve this custom data when the notification is delivered.

Scheduling the notification

        [[UIApplication sharedApplication] scheduleLocalNotification:notif];
        [notif release];

Finally after going though (nearly) all the options you call a method on the application instance to actually schedule the notification after which we can release the allocated notification object.

Handling the Notification

There are three different situations to consider when handling the delivery of a notification to the application:

  • application is not running (either in the foreground or background)
  • application is in the background (suspended or running a background task)
  • application is already running in the foreground (active)

Note that Apple documentation is confusing in that it does not distinguish between the situations where the application is not running at all and when it is in the background.

Application not running

If the application is not actually running when the notification is delivered the system displays the alert if configured with the alertAction button. If the user taps the button the application is launched and the application delegate method application:didFinishLaunchingWithOptions: is called.

Note if your application currently uses applicationDidFinishLaunching you should convert to the newer method in order to access the launch options.

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    Class cls = NSClassFromString(@"UILocalNotification");
    if (cls) {
        UILocalNotification *notification = [launchOptions objectForKey:
                            UIApplicationLaunchOptionsLocalNotificationKey];

        if (notification) {
           NSString *reminderText = [notification.userInfo 
                            objectForKey:kRemindMeNotificationDataKey];
           [viewController showReminder:reminderText];
        }
    }

    application.applicationIconBadgeNumber = 0;

    [window addSubview:viewController.view];
    [window makeKeyAndVisible];

    return YES;
}

As before we first check to see if the UILocalNotification class exists to maintain backward compatibility with iOS 3.x versions. Then we check the launchOptions dictionary to see if it contains a local notification object. If it does we retrieve our custom data from the userInfo dictionary and pass it to the view controller to show the reminder text to the user.

Application is in the background

In the application is the background (either running a background task or suspended) when the notification is delivered the system displays the alert dialog as before. If the users taps the action button the application is brought to the foreground and the application delegate method didReceiveLocalNotification: will be called and passed the notification object:

- (void)application:(UIApplication *)application 
        didReceiveLocalNotification:(UILocalNotification *)notification {

    application.applicationIconBadgeNumber = 0;
    NSString *reminderText = [notification.userInfo
                              objectForKey:kRemindMeNotificationDataKey];
    [viewController showReminder:reminderText];
}

The handling is in this case similar to the situation when the application is launched. The custom data is retrieved from the userInfo dictionary contained in the notification and passed to our viewController method for display to the user.

Application in the foreground

The situation when the application is already running in the foreground is very similar to when it is in the background with the exception that the system does not display the notification alert to the user. The application delegate will have its didReceiveLocalNotification: method called as when it was running in the background. If you want to distinguish between these two situations you can check the application state:

- (void)application:(UIApplication *)application 
        didReceiveLocalNotification:(UILocalNotification *)notification {
	
    UIApplicationState state = [application applicationState];
    if (state == UIApplicationStateInactive) {

        // Application was in the background when notification
        // was delivered.
    }

Downloading the example code

If you would like to download the complete Xcode project for this example you can find it here. I hope it proves useful.

PrintView Printer Friendly Version

EmailEmail Article to Friend

References (1)

References allow you to track sources for this article, as well as articles that were written in response to this article.

Reader Comments (33)

Very informative post. This is the only helpful info I have seen for UILocalNotifications. I recently submitted my updated app to the app store including local notifications. Do you know of anyway to increment the badge number instead of just setting it 1 for every notification. For example, if the user didn't open the app and the badge has a 1, when the next notification fires it would increment to 2?

August 4, 2010 | Unregistered Commenterchris sheldrick

@Chris - I am not aware of any way to get the notification to increment the badge value directly - would be very useful though. Depending on how your app works you can sometimes get around it by cancelling outstanding notifications when your app launches (or comes to the foreground) and reissuing notifications with the correct badge number when terminating or moving to the background.

August 4, 2010 | Registered CommenterKeith

Hi Keith,

Really nice tutorial! only one question.. how can i fire a daily notification?
Hoop you can help me!

Thanks in advance

August 31, 2010 | Unregistered CommenterMarcos

@Marcos. Not sure if it is what you want but did you take a look at setting the repeatInterval property of UILocalNotification? The property is an NSCalendarUnit so if you set it to NSDayCalendarUnit the notification should be repeated daily. If you only want the notification to repeat on weekdays and not at weekends you can use NSWeekdayCalendarUnit. I have never used these so if I get a chance I will play with them and post the results.

August 31, 2010 | Registered CommenterKeith

Many thanks!

I'm just started to have a look at this and your post answered all my questions. Perfect level of detail!

September 9, 2010 | Unregistered CommenterStructurer

Bravo, your tutorial is great ! thank you for your time

September 30, 2010 | Unregistered Commenterrere46

I have reason to believe that the implementation of applicationIconBadgeNumber is buggy, or at least not according to what it has been described to do in the documentation.

I've filed a bug report/enhancement at http://bugreport.apple.com/

You may take a look at my report archived at Open Radar: http://openradar.appspot.com/radar?id=767401

Meanwhile, I need to maintain my own count of notifications and schedule a duplicate set of UILocalNotification just to give to illusion of incrementing badges.

According to Marco, developer of Instapaper, Apple prioritizes enhancement and feature requests partly based on how many developers are requesting them.

If this feature is important to you I urge you to file a report as well.

October 20, 2010 | Unregistered CommenterLin Junjie

Absolutely prefect - just what I needed. MAny thanks for making the effort to post this. Very clear and concise.

November 1, 2010 | Unregistered CommenterDavid Griffiths

Thanks! I'm preparing a very simple App using your article, and adding notifications to other of my Apps. Much easier after reading your post

January 24, 2011 | Unregistered CommenterDiego Freniche

I have just added local notifications to my app and have also noticed the problem with incrementing the application badge number properly. I would like the badge number to increment only when the user chooses not to view the alert which is fired. It lets them know how many alerts they have ignored. Then, when the user goes into my app to view each alert, I can properly decrement the badge number as they view each alert.

But it seems like that's not possible to do.

I guess I'm going to just have to set the value to 1 for every new notification created and that will just have to imply that there are some notifications outstanding. But that I'm sure is going to be counter intuitive to my users.

Thanks,

Brendan Duddridge
www.tapforms.com

February 19, 2011 | Unregistered CommenterBrendan Duddridge

Oh, by the way, I also filed a radar bug that references the bug at http://openradar.appspot.com/radar?id=767401

Thanks!

Brendan

February 19, 2011 | Unregistered CommenterBrendan Duddridge

Hi Keith,

Is there a way to edit the LocalNotification? Or is it automatically updated if you updated the current object with a new date?

March 7, 2011 | Unregistered CommenterAngelo

@Angelo. I think you will need to cancel the existing notification and recreate it with the new fire date.

March 7, 2011 | Registered CommenterKeith

Thanks for your example. It helped a lot in my app. It would be great if you can tell us how to cancel the existing notifications.

March 18, 2011 | Unregistered CommenterRavi Katiyar

Hi Keith,

Really nice tutorial, one question though - Can I set a local notification to be processed by my app without user interaction?

Thanks in advance

April 7, 2011 | Unregistered CommenterAvi

@Avi - if your application is running in the foreground when the notification arrives you can implement application:didReceiveLocalNotification in your application delegate. No alert is presented to the user in this case. However you cannot rely on the fact that your application will be running or if it is running that it is in the foreground. If your application is not running or is in the background I don't think there is currently a way to intercept the notification without user interaction.

April 8, 2011 | Registered CommenterKeith

In a local notification alert, is it possible to display only the action 'View' button? I want the user to always open the application when an alert is displayed. Basically I don't want the default button 'Close'.
Thanks

April 17, 2011 | Unregistered CommenterDan

@Dan I don't think there is a way you can force the user to open the app when a notification is delivered - they can always cancel if they want to ignore the alert. Even if you could it would probably not be a great user experience.

April 17, 2011 | Unregistered CommenterKeith Harrison

This post and others are awesome! Well written and easy to read. I have just started an iPhone development blog myself and am wondering if you would like to submit an article / tutorial to my blog for a link Back? Either reply via email or twitter @ios_blog

April 19, 2011 | Unregistered CommenteriPhone Development

@ios_blog that is a cool looking blog, puts me to shame.

April 21, 2011 | Registered CommenterKeith

Hi Keith

Indeed great posting - very helpful.
I am facing 2 issues and I will appreciate your assistance, both relates to suspend mode when iPhone is locked:

1. Loose notification while device is locked: I gets a notification with "Slide to show" message. If I do not response at that time, screen is shut off. When I press the home button, screen switch on and notification message is still on screen. Now I "Slide to unlock", my application do not goes to foreground and I do not know I was suppose to do something there. Can I enter automatically to foreground state in this stage?

2. Alternatively, I wish to update batch value in this situation so user will enter the application manually?

TNX

May 23, 2011 | Unregistered CommenterYair

Keith, Thank you! I am soon going to be releasing it, well the version 2 out into the world :)

- You have not answered my question, would you like to submit an article or two to my blog? I will link back to yours and give you full credit etc... This could help us both out. Follow the twitter @ios_blog too and i'll return the favour :-p

May 23, 2011 | Unregistered CommenteriPhone Developmen

@Yair - I have seen that and it is frustrating. The best thing you can do is to also set the badge number in the notification so that there is always a visual cue for the user that something happened.

May 24, 2011 | Unregistered CommenterKeith

@iPhone Developmen - I don't really have time to write additional posts right now but thanks for the offer

May 24, 2011 | Registered CommenterKeith

Hi,

I implemented local notification in my app but I am just wondering is there a way to play a sound that is not part of the static bundle.
Basically in my app, I want user to record a sound that gets played when the local notification is generated instead of playing a pre-recorded sound.

Anyone has any idea how I can do it ?
Any help will be greatly appreciated.

August 18, 2011 | Unregistered CommenterFaisal Faruqi

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>