Sunday, October 2, 2011

Local Notifications (iOS 4)

With the coming of iPhone OS 3, push notifications were introduced which enables an application to alert user while the application isn't running. Push notifications solved many of the issues associated with background processing. For example, when quitting the Facebook application, the server could keep you logged in and send you a push notification when a new message arrived. You could then tap on a View button that would launch the app.
This solution is great and all, but it still requires that you have an active internet connection. As of iOS4, Apple has introduced a new type of notification that can be scheduled to fire within the device itself. It requires no complicated server programming. These are known as Local Notifications.

Local notifications can be scheduled on the user’s device to fire at any given time; you can even set them to be recurring. Today, we will explore these notifications and go through a simple example of how to schedule, view, and handle local notifications.

The project will allow a user to schedule a location notification to fire off at a given date. Using the text field, they are also able to specify some text for the notification. The table view below displays a list of all of the currently scheduled location notifications within the application.

Step 1
Create a View Based Applicatin and name it LocalNotifier.

Step 2

Create all properties and dummy IBActions. Copy the code below in your .h file
@interface LocalNotifierViewController : UIViewController {
IBOutlet UITableView *tableview;
IBOutlet UIDatePicker *datePicker;
IBOutlet UITextField *eventText;
}

@property (nonatomic, retain) IBOutlet UITableView *tableview;
@property (nonatomic, retain) IBOutlet UIDatePicker *datePicker;
@property (nonatomic, retain) IBOutlet UITextField *eventText;

- (IBAction) scheduleAlarm:(id) sender;

@end


We have defined three fields, one is a TableView to dipslay all the scheduled notifications, another is a TextField where use enters the event description of any new notification to be scheduled and the last field is datePicker from where user picks the date when he wants to schedule the new notification or wants to see all notifications for that day.

Next, setup the .m file of your viewController.
@synthesize datePicker,tableview, eventText;

- (IBAction) scheduleAlarm:(id) sender {

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
datePicker = nil;
tableview = nil;
eventText = nil;
}

- (void)dealloc {
[super dealloc];
}


Now it’s time to build our interface. Open Interface builder and construct an interface like this.



Step 3: Implement UITableViewDelegate and UITableViewDataSource Delegate methods to List Currently Scheduled Local Notifications

Add the following code in your .m file


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// We only have one section
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of notifications
return [[[UIApplication sharedApplication] scheduledLocalNotifications] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}

// Get list of local notifications
NSArray *notificationArray = [[UIApplication sharedApplication] scheduledLocalNotifications];
UILocalNotification *notif = [notificationArray objectAtIndex:indexPath.row];

// Display notification info
[cell.textLabel setText:notif.alertBody];
[cell.detailTextLabel setText:[notif.fireDate description]];

return cell;
}


So, the new code here is dealing with retrieving a list of scheduled notifications. Calling the scheduledLocalNotifications method of UIApplication will return an NSArray of all notifications scheduled by the current app. We just index into this array and grab each notification.

Finally, we are displaying the alertBody (text that displays when the notification fires) and the fireDate (date and time when the notification will display) in the tableview cell.

Step 4: Scheduling Notifications
Update your .m file to contain the following code.


- (IBAction) scheduleAlarm:(id) sender {
[eventText resignFirstResponder];

NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];

// Get the current date
NSDate *pickerDate = [self.datePicker date];

// Break the date up into components
NSDateComponents *dateComponents = [calendar components:( NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit )
fromDate:pickerDate];
NSDateComponents *timeComponents = [calendar components:( NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit )
fromDate:pickerDate];
// Set up the fire time
NSDateComponents *dateComps = [[NSDateComponents alloc] init];
[dateComps setDay:[dateComponents day]];
[dateComps setMonth:[dateComponents month]];
[dateComps setYear:[dateComponents year]];
[dateComps setHour:[timeComponents hour]];
// Notification will fire in one minute
[dateComps setMinute:[timeComponents minute]];
[dateComps setSecond:[timeComponents second]];
NSDate *itemDate = [calendar dateFromComponents:dateComps];
[dateComps release];

UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil)
return;
localNotif.fireDate = itemDate;
localNotif.timeZone = [NSTimeZone defaultTimeZone];

// Notification details
localNotif.alertBody = [eventText text];
// Set the action button
localNotif.alertAction = @"View";

localNotif.soundName = UILocalNotificationDefaultSoundName;
localNotif.applicationIconBadgeNumber = 1;

// Specify custom data for the notification
NSDictionary *infoDict = [NSDictionary dictionaryWithObject:@"someValue" forKey:@"someKey"];
localNotif.userInfo = infoDict;

// Schedule the notification
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
[localNotif release];

[self.tableview reloadData];
}


We just use the NSCalendar object to break up the date into components. Note: This demo does not require that we break the date up into components. You could have just as easily fed the date from the date picker into the notification fireDate. The reason that I’m showing you how to break it down is, you may have some sort of custom date logic to work with and this makes things much easier in the future.
Another important bit of code is where we set the alertBody or the notification. In this example we set it to the text that the user entered into the text field. You can set this to whatever you like.
The other thing I want to mention is the infoDict in the code. This dictionary is your chance to associate some additional information with the alert. For example, if you are using this alert in a game like We Rule to notify you when a crop is ready. You might want to set a key and value that contains the id of the crop that has completed. For now, we just set some arbitrary values and you can ignore them if you like.
After actually scheduling the notification, we just reload the tableview to get it to display immediately.

Step 5: Handling Notifications After They Fire
The last piece of this puzzle is determining what to do when a notification fires. Fortunately, this step is very easy and handled inside of the appDelegate. When a notification fires, there are one of two situations. 1. The app is running and 2. The app is not running (or running in the “background”) .

Open up your app delegate .m file and add the following code.


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

// Override point for customization after application launch.

// Add the view controller's view to the window and display.
[window addSubview:viewController.view];
[window makeKeyAndVisible];

application.applicationIconBadgeNumber = 0;

// Handle launching from a notification
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif) {
NSString *reminderText = [notification.userInfo
objectForKey:@"someKey"];
NSLog(@"Recieved Notification %@",localNotif);
}

return YES;
}

- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif {
// Handle the notificaton when the app is running
NSLog(@"Recieved Notification %@",notif);
}


The first thing we see here is the application badge number is getting set to 0. Whenever a notification fires, it will increase the badge count on the application. Next, we handle the case when the application launches from a notification. This happens when the user presses the view button on the notification. For now, we just NSLog the data, but you should handle the notification how you see fit for your app.

Finally, we implement the didReceiveLocalNotification method. This method is required if you want to handle notifications at all in your app. You will see this method fire when the app is running and you receive a local notification. When the app is running, you will not see the UIAlertView show up with the notification data. Also keep a note the method - (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
is only called upon when the application is not running and if the application is running in background or foreground, only this methhod gets called: - (void)application:(UIApplication *)application
didReceiveLocalNotification:(UILocalNotification *)notification


And there you have it! The complete lifecycle of a local notification.