iOS Reference Library Apple Developer
Search

Scheduling, Registering, and Handling Notifications

This chapter describes the tasks that a iPhone, iPad, or iPod touch application should (or might) do to schedule local notifications, register remote notifications, and handle both local and remote notifications. Because the client-side API for push notifications refers to push notifications as “remote notifications,” that terminology is used in this chapter.

Preparing Custom Alert Sounds

You can specify a custom sound that iOS plays when it presents a local or remote notification for an application. The sound files must be in the main bundle of the client application.

Because custom alert sounds are played by the iOS system-sound facility, they must be in one of the following audio data formats:

You can package the audio data in an aiff, wav, or caf file. Then, in Xcode, add the sound file to your project as a nonlocalized resource of the application bundle.

You may use the afconvert tool to convert sounds. For example, to convert the 16-bit linear PCM system sound Submarine.aiff to IMA4 audio in a CAF file, use the following command in the Terminal application:

afconvert /System/Library/Sounds/Submarine.aiff ~/Desktop/sub.caf -d ima4 -f caff -v

You can inspect a sound to determine its data format by opening it in QuickTime Player and choosing Show Movie Inspector from the Movie menu.

Custom sounds must be under 30 seconds when played. if a custom sound is over that limit, the default system sound is played instead.

Scheduling Local Notifications

Creating and scheduling local notifications requires that you perform a few simple steps:

  1. Allocate and initialize a UILocalNotification object.

  2. Set the date and time that the operating system should deliver the notification. This is the fireDate property.

    If you set the timeZone property to the NSTimeZone object for the current locale, the system automatically adjusts the fire date when the device travels across (and is reset for) different time zones. (Time zones affect the values of date components—that is, day, month, hour, year, hour, and minute—that the system calculates for a given calendar and date value.) You can also schedule the notification for delivery on a recurring basis (daily, weekly, monthly, and so on).

  3. Configure the substance of the notification: alert, icon badge number, and sound.

    • The alert has a property for the message (the alertBody property) and for the title of the action button or slider (alertAction); both of these string values can be internationalized for the user‚Äôs current language preference.

    • You set the badge number to display on the application icon through the applicationIconBadgeNumber property.

    • You can assign the filename of a nonlocalized custom sound in the application‚Äôs main bundle to the soundName property; to get the default system sound, assign UILocalNotificationDefaultSoundName. Sounds should always accompany an alert message or icon badging; they should not be played otherwise.

  4. Optionally, you can attach custom data to the notification through the userInfo property.

    Keys and values in the userInfo dictionary must be property-list objects.

  5. Schedule the local notification for delivery.

    You schedule a local notification by calling the UIApplication method scheduleLocalNotification:. The application uses the fire date specified in the UILocalNotification object for the moment of delivery. Alternatively, you can present the notification immediately by calling the presentLocalNotificationNow: method.

The method in Listing 2-1 creates and schedules a notification to inform the user of a hypothetical to-do list application about the impending due date of a to-do item. There are a couple things to note about it. For the alertBody and alertAction properties, it fetches from the main bundle (via the NSLocalizedString macro) strings localized to the user’s preferred language. It also adds the name of the relevant to-do item to a dictionary assigned to the userInfo property.

Listing 2-1  Creating, configuring, and scheduling a local notification

- (void)scheduleNotificationWithItem:(ToDoItem *)item interval:(int)minutesBefore {
    NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
    NSDateComponents *dateComps = [[NSDateComponents alloc] init];
    [dateComps setDay:item.day];
    [dateComps setMonth:item.month];
    [dateComps setYear:item.year];
    [dateComps setHour:item.hour];
    [dateComps setMinute:item.minute];
    NSDate *itemDate = [calendar dateFromComponents:dateComps];
    [dateComps release];
 
    UILocalNotification *localNotif = [[UILocalNotification alloc] init];
    if (localNotif == nil)
        return;
    localNotif.fireDate = [itemDate addTimeInterval:-(minutesBefore*60)];
    localNotif.timeZone = [NSTimeZone defaultTimeZone];
 
    localNotif.alertBody = [NSString stringWithFormat:NSLocalizedString(@"%@ in %i minutes.", nil),
         item.eventName, minutesBefore];
    localNotif.alertAction = NSLocalizedString(@"View Details", nil);
 
    localNotif.soundName = UILocalNotificationDefaultSoundName;
    localNotif.applicationIconBadgeNumber = 1;
 
    NSDictionary *infoDict = [NSDictionary dictionaryWithObject:item.eventName forKey:ToDoItemKey];
    localNotif.userInfo = infoDict;
 
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
    [localNotif release];
}

You can cancel a specific scheduled notification by calling cancelLocalNotification: on the application object, and you can cancel all scheduled notifications by calling cancelAllLocalNotifications. Both of these methods also programmatically dismiss a currently displayed notification alert.

Applications might also find local notifications useful when they run in the background and some message, data, or other item arrives that might be of interest to the user. In this case, they should present the notification immediately using the UIApplication method presentLocalNotificationNow: (iOS gives an application a limited time to run in the background). Listing 2-2 illustrates how you might do this.

Listing 2-2  Presenting a local notification immediately while running in the background

- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSLog(@"Application entered background state.");
    // bgTask is instance variable
    NSAssert(self->bgTask == UIInvalidBackgroundTask, nil);
 
    bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            [application endBackgroundTask:self->bgTask];
            self->bgTask = UIInvalidBackgroundTask;
        });
    }];
 
    dispatch_async(dispatch_get_main_queue(), ^{
        while ([application backgroundTimeRemaining] > 1.0) {
            NSString *friend = [self checkForIncomingChat];
            if (friend) {
                UILocalNotification *localNotif = [[UILocalNotification alloc] init];
                if (localNotif) {
                    localNotif.alertBody = [NSString stringWithFormat:
                        NSLocalizedString(@"%@ has a message for you.", nil), friend];
                    localNotif.alertAction = NSLocalizedString(@"Read Message", nil);
                    localNotif.soundName = @"alarmsound.caf";
                    localNotif.applicationIconBadgeNumber = 1;
                    [application presentLocalNotificationNow:localNotif];
                    [localNotif release];
                    friend = nil;
                    break;
                }
            }
        }
        [application endBackgroundTask:self->bgTask];
        self->bgTask = UIInvalidBackgroundTask;
    });
 
}

Registering for Remote Notifications

An application must register with Apple Push Notification service for iOS to receive remote notifications sent by the application’s provider. Registration has three stages:

  1. The application calls the registerForRemoteNotificationTypes: method of UIApplication.

  2. It implements the application:didRegisterForRemoteNotificationsWithDeviceToken: method of UIApplicationDelegate to receive the device token.

  3. It passes the device token to its provider as a non-object, binary value.

What happens between the application, the device, Apple Push Notification Service, and the provider during this sequence is illustrated by Figure 3-3 in “Token Generation and Dispersal.”

An application should register every time it launches and give its provider the current token. It calls registerForRemoteNotificationTypes: to kick off the registration process. The parameter of this method takes a UIRemoteNotificationType bit mask that specifies the initial types of notifications that the application wishes to receive—for example, icon-badging and sounds, but not alert messages. Users can thereafter modify the enabled notification types in the Notifications preference of the Settings application, and you can retrieve the currently enabled notification types by calling the enabledRemoteNotificationTypes method. iOS does not badge icons, display alert messages, or play alert sounds if any of these notifications types are not enabled, even if they are specified in the notification payload.

If registration is successful, APNs returns a device token to the device and iOS passes the token to the application delegate in the application:didRegisterForRemoteNotificationsWithDeviceToken: method. The application should connect with its provider and pass it this token, encoded in binary format. If there is a problem in obtaining the token, iOS informs the delegate by calling the application:didFailToRegisterForRemoteNotificationsWithError: method. The NSError object passed into this method clearly describes the cause of the error. The error might be, for instance, an erroneous aps-environment value in the provisioning profile. (See “Creating and Installing the Provisioning Profile” for details.)

Note: If a cellular or Wi-Fi connection is not available, neither the application:didRegisterForRemoteNotificationsWithDeviceToken: method or the application:didFailToRegisterForRemoteNotificationsWithError: method is called. For Wi-Fi connections, this sometimes occurs when the device cannot connect with APNs over port 5223. If this happens, the user can move to another Wi-Fi network that isn‚Äôt blocking this port or, on an iPhone or iPad, wait until the cellular data service becomes available. In either case, the connection should then succeed and one of the delegation methods is called.

By requesting the device token and passing it to the provider every time your application launches, you help to ensure that the provider has the current token for the device. If a user restores a backup to a device other than the one that the backup was created for (for example, the user migrates data to a new device), he or she must launch the application at least once for it to receive notifications again. If the user restores backup data to a new device or reinstalls the operating system, the device token changes. Moreover, never cache a device token and give that to your provider; always get the token from the system whenever you need it. If your application has previously registered, calling registerForRemoteNotificationTypes: results in iOS passing the device token to the delegate immediately without incurring additional overhead.

Listing 2-3 gives a simple example of how you might register for remote notifications. (SendProviderDeviceToken is a hypothetical method defined by the client in which it connects with its provider and passes it the device token.)

Listing 2-3  Registering for remote notifications

- (void)applicationDidFinishLaunching:(UIApplication *)app {
   // other setup tasks here....
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}
 
// Delegation methods
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
    const void *devTokenBytes = [devToken bytes];
    self.registered = YES;
    [self sendProviderDeviceToken:devTokenBytes]; // custom method
}
 
- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
    NSLog(@"Error in registration. Error: %@", err);
}

Handling Local and Remote Notifications

Let’s review the possible scenarios when iOS delivers a remote or local notification for an application.

You can determine whether an application is launched as a result of the user tapping the action button or whether the notification was delivered to the already-running application by examining the application state. In the delegate’s implementation of the application:didReceiveRemoteNotification: or application:didReceiveLocalNotification: method, get the value of the applicationState property and evaluate it. If the value is UIApplicationStateInactive, the user tapped the action button; if the value is UIApplicationStateActive, the application was frontmost when it received the notification.

An application can use the passed-in remote-notification payload or the UILocalNotification object to help set the context for processing the item related to the notification. Ideally, the delegate should adopt the UIApplicationDelegate protocol and implement both the application:didFinishLaunchingWithOptions: method and the application:didReceiveRemoteNotification: or application:didReceiveLocalNotification: method to handle the delivery of notifications in all situations.

The application delegate in Listing 2-4 implements the application:didFinishLaunchingWithOptions: method to handle a local notification. It gets the associated UILocalNotification object from the launch-options dictionary using the UIApplicationLaunchOptionsLocalNotificationKey key. From the UILocalNotification object’s userInfo dictionary, it access the to-do item that is the reason for the notification and uses it to set the application’s initial context. As shown in this example, you should appropriately reset the badge number on the application icon—or remove it if there are no outstanding items—as part of handling the notification.

Listing 2-4  Handling a local notification when an application is launched

- (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UILocalNotification *localNotif =
        [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    if (localNotif) {
        NSString *itemName = [localNotif.userInfo objectForKey:ToDoItemKey];
        [viewController displayItem:itemName];  // custom method
        application.applicationIconBadgeNumber = localNotif.applicationIconBadgeNumber-1;
    }
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
    return YES;
}

The implementation for a remote notification would be similar, except that you would use the UIApplicationLaunchOptionsRemoteNotificationKey key to access the payload from the launch-options dictionary. The payload itself is an NSDictionary object that contains the elements of the notification—alert message, badge number, sound, and so on. It can also contain custom data the application can use to provide context when setting up the initial user interface. See “The Notification Payload” for details about the remote-notification payload.

Important: You should never define custom properties in the notification payload for the purpose of transporting customer data or any other sensitive data. Delivery of remote notifications is not guaranteed. One example of an appropriate usage for a custom payload property is a string identifying an email account from which messages are downloaded to an email client; the application can incorporate this string in its download user-interface. Another example of custom payload property is a timestamp for when the provider first sent the notification; the client application can use this value to gauge how old the notification is.

When handling remote notifications in application:didFinishLaunchingWithOptions:, the application delegate might perform a major additional task. Just after the application launches, the delegate should connect with its provider and fetch the waiting data. Listing 2-4 gives a schematic illustration of this procedure,.

Listing 2-5  Downloading data from a provider

- (void)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)opts {
    // check launchOptions for notification payload and custom data, set UI context
    [self startDownloadingDataFromProvider];  // custom method
    app.applicationIconBadgeNumber = 0;
    // other setup tasks here....
}

Note: A client application should always communicate with its provider asynchronously or on a secondary thread.

The code in Listing 2-6 shows an implementation of the application:didReceiveLocalNotification: method which, as you’ll recall, is called when application is running in the foreground. Here the application delegate does the same work as it does in Listing 2-4. It can access the UILocalNotification object directly this time because this object is an argument of the method.

Listing 2-6  Handling a local notification when an application is already running

- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif {
    NSString *itemName = [notif.userInfo objectForKey:ToDoItemKey]
    [viewController displayItem:itemName];  // custom method
    application.applicationIconBadgeNumber = notification.applicationIconBadgeNumber-1;
}

If you want your application to catch remote notifications that the system delivers while it is running in the foreground, the application delegate should implement the application:didReceiveRemoteNotification: method of UIApplicationDelegate. The delegate should begin the procedure for downloading the waiting data, message, or other item and, after this concludes, it should remove the badge from the application icon. (If your application frequently checks with its provider for new data, implementing this method might not be necessary.) The dictionary passed in the second parameter of this method is the notification payload; you should not use any custom properties it contains to alter your application’s current context.

If the user unlocks the device shortly after a remote-notification alert is displayed, the operating system automatically triggers the action associated with the alert. (This behavior is consistent with SMS and calendar alerts.) This makes it even more important that actions related to remote notifications do not have destructive consequences. A user should always make decisions that result in the destruction of data in the context of the application that stores the data.

Passing the Provider the Current Language Preference (Remote Notifications)

If an application doesn’t use the loc-key and loc-args properties of the aps dictionary for client-side fetching of localized alert messages, the provider needs to localize the text of alert messages it puts in the notification payload. To do this, however, the provider needs to know the language that the device user has selected as the preferred language. (The user sets this preference in the General > International > Language view of the Settings application.) The client application should send its provider an identifier of the preferred language; this could be a canonicalized IETF BCP 47 language identifier such as “en” or “fr”.

Note: For more information about the loc-key and loc-args properties and client-side message localizations, see ‚ÄúThe Notification Payload.‚Äù

Listing 2-7 illustrates a technique for obtaining the currently selected language and communicating it to the provider. In iOS, the array returned by the preferredLanguages of NSLocale contains one object: an NSString object encapsulating the language code identifying the preferred language. The UTF8String coverts the string object to a C string encoded as UTF8.

Listing 2-7  Getting the current supported language and sending it to the provider

NSString *preferredLang = [[NSLocale preferredLanguages] objectAtIndex:0];
const char *langStr = [preferredLang UTF8String];
[self sendProviderCurrentLanguage:langStr]; // custom method
}

The application might send its provider the preferred language every time the user changes something in the current locale. To do this, you can listen for the notification named NSCurrentLocaleDidChangeNotification and, in your notification-handling method, get the code identifying the preferred language and send that to your provider.

If the preferred language is not one the application supports, the provider should localize the message text in a widely spoken fallback language such as English or Spanish.




Last updated: 2010-08-03

Did this document help you? Yes It's good, but... Not helpful...