iOS Components and Frameworks: Understanding the Advanced Features of the iOS SDK (2014)
Chapter 9. Notifications
Notifications are Apple’s method of keeping the user informed of important iOS app-related events when you are not actively using an app. Because only one iOS app can be active and in the foreground at a time, notifications provide a mechanism to have inactive apps receive important and time-sensitive information and notify the user. This chapter will guide you through how to set up your app to receive local and remote push notifications, and how to customize what happens when the user receives a notification with an app badge, a sound, and a message. This chapter will also guide you through how to set up your own Ruby on Rails server to respond to events and push out notifications.
Differences Between Local and Push Notifications
Two types of notifications are supported by iOS: local notifications and remote, or push, notifications. Local notifications do not use or require any external infrastructure; they happen entirely on the device. That means that the device does not require any connectivity—besides being “on”—to present a local notification. Push notifications, however, require connectivity and a server infrastructure of some kind to send the notification through the Apple Push Notification service (APNs) to the intended device. It is important to note that push notification delivery is not guaranteed, so it is not appropriate to assume that every notification gets to its intended target. Do not make your application depend on push notifications.
To understand why push notification delivery is not guaranteed, you need to understand how push notifications get to a device. The APNs first will try to use the cellular network to communicate with an iOS device if it is available, then will attempt over Wi-Fi. Some devices need to be in an active state (on fourth-generation iPod touches, for example, the screen actually has to be visible) in order to receive Wi-Fi communication. Other devices, like iPads, can be asleep and maintain a connection to a Wi-Fi network.
The process that each type of notification goes through is also different. For local notifications these are the steps:
1. Create a local notification object, and specify options like schedule time and date, message, sound, and badge update.
2. Schedule the local notification.
3. iOS presents the notification, plays a sound, and updates the badge.
4. Receive the local notification in the application delegate.
For push notifications this is the process:
1. Register the application for push notifications and receive a token.
2. Notify your server that the device (identified by a token) would like to receive push notifications.
3. Create a push notification on your server and communicate to APNs.
4. APNs delivers the notification to the device.
5. iOS presents the notification, plays a sound, and updates the badge.
6. Receive the notification in the application’s delegate.
With these differences, it’s clear that there are different use cases in which local and push notifications make sense. If no information from outside the device is required, use a local notification. If information not available to the device is required, use a push notification.
Sample App
The sample app for this chapter is called ShoutOut. It allows the user to Shout Out a message to all the other users of the app via a push notification and to add reminders to Shout Out. The sample app will illustrate setting up reminders as local notifications, as well as all the steps necessary to set up an app to receive push notifications, to communicate with a server, and to have the server send push notifications via the APNs.
App Setup
There are several steps to prepare the app for push notifications. To begin, set up an App ID in the iOS Provisioning Portal. Visit the iOS Dev Center (https://developer.apple.com/devcenter/ios/index.action), log in, and choose Certificates, Identifiers & Profiles in the menu titled iOS Developer Program on the right side of the screen (you must be logged in to see this menu). Choose Identifiers from the menu on the left side of the screen. Then, click the button with a plus sign in the upper-right corner to create a new App ID, as shown in Figure 9.1.
Figure 9.1 iOS Provisioning Portal: Registering an App ID, App ID Description and Prefix.
Specify a Description for the App ID. The Description will be used to display the app throughout the iOS Provisioning Portal. Select an App ID Prefix (previously called the Bundle Seed ID). Scroll down to specify the App ID Suffix, as shown in Figure 9.2.
Figure 9.2 iOS Provisioning Portal: Registering an App ID, App ID Suffix.
Push notifications require an explicit App ID, so select that option and specify the same string as the Bundle ID for your app. Scroll down to select App Services, as shown in Figure 9.3.
Figure 9.3 iOS Provisioning Portal: Registering an App ID, App Services.
Select the check box for Push Notifications in the list of App Services to indicate that push notifications should be enabled for the App ID. Click Continue to save the new App ID, and it will be visible in the list of App IDs. Click on the App ID to expand it and view the status of services for the App ID, as shown in Figure 9.4.
Figure 9.4 iOS Provisioning Portal: App ID in list.
Now that the App ID is prepared, it needs to be configured for push notifications. Click Settings at the bottom of the App ID detail list, and scroll to the bottom to view the push notifications, as shown in Figure 9.5.
Figure 9.5 iOS Provisioning Portal: App ID Push Notifications settings.
Make sure that Enabled for Apple Push Notification service is checked. If so, the App ID is ready and push certificates can be created.
Create Development Push SSL Certificate
A Development Push SSL Certificate is what the push server uses to identify and authorize a specific account to APNs when connecting to APNs to send push notifications. To start the process of creating a certificate, click the Create Certificate button on the Development line (refer to Figure 9.5). Instructions will be presented to help generate a certificate signing request, as shown in Figure 9.6.
Figure 9.6 iOS Provisioning Portal: About Creating a Certificate Signing Request (CSR).
Leave the Add iOS Certificate page open in the browser, and open Keychain Access (in Applications, Utilities). Select Keychain Access, Certificate Assistant, Request a Certificate from a Certificate Authority from the application menu. A certificate request form will be presented, as shown in Figure 9.7.
Figure 9.7 Keychain Access Certificate Assistant.
Enter an email address, a common name (typically a company name or an entity name—it is safe to use your Apple Developer account name), and then select Saved to Disk. Click Continue, and specify where to save the request. When that step is complete, return to the iOS Provisioning Portal and click Continue. Select the saved request, as shown in Figure 9.8.
Figure 9.8 iOS Provisioning Portal: Add iOS Certificate—Generate Your Certificate.
After selecting it, click Generate. The development SSL certificate will be generated, as shown in Figure 9.9.
Figure 9.9 iOS Provisioning Portal: Add iOS Certificate—Your Certificate Is Ready.
After your certificate has been created, click the Download button to download the certificate so that the certificate can be installed on the notification server.
Double-click the downloaded certificate file, and it will automatically be installed in Keychain Access. It should be visible in the list of certificates, as shown in Figure 9.10. Click on the triangle to confirm that the private key was delivered with the certificate.
Figure 9.10 Keychain Access: Apple Development iOS Push Services SSL Certificate and private key.
Because you have not yet set up the notifications server, you will not yet take the next step to install the certificate on the server; you will come back to that after we complete the server setup steps later in this chapter. Also note that this procedure needs to be repeated for your Production SSL Certificate when you are ready to distribute your application.
Development Provisioning Profile
For most apps, it is sufficient to use the development provisioning profile automatically generated by Xcode. If you are testing push notifications, however, you need to create and use a development provisioning profile specific to your app to allow it to receive push notifications. To do this, stay in the iOS Provisioning Portal where you created the App ID and SSL Certificate, and click Provisioning Profiles in the left menu.
Note
This presumes that you have already created a development certificate (under Certificates, Development); if not, you should create one first. The procedure is well documented in the portal and similar to creating the SSL Certificate. This also presumes that you have set up at least one device for development in the portal; if not, you will need to do that as well (under Devices).
You will be presented with a list of development provisioning profiles. To create a new one, click the button with the plus sign just above and to the right of the list. Select which type of provisioning profile to create (in this case, iOS App Development) and click Continue, as shown in Figure 9.11.
Figure 9.11 iOS Provisioning Profile: Add iOS Provisioning Profile.
Select the App ID just created, as shown in Figure 9.12, and click Continue.
Figure 9.12 iOS Provisioning Profile: Add iOS Provisioning Profile—Select App ID.
Next select the Development Certificate(s) to be used when signing the app with this provisioning profile, as shown in Figure 9.13, and click Continue.
Figure 9.13 iOS Provisioning Profile: Add iOS Provisioning Profile—Select Certificates.
Select the devices that can be used to run the app using this provisioning profile, as shown in Figure 9.14. It is generally a good practice to select all available devices to prevent having to regenerate the provisioning profile when it is discovered that a team member was not added the first time around.
Figure 9.14 iOS Provisioning Profile: Add iOS Provisioning Profile—Select Devices.
Finally, provide a name for the provisioning profile, and review the summary presented for the provisioning profile, as shown in Figure 9.15. If the profile looks correct, click Generate to create it.
Figure 9.15 iOS Provisioning Profile: Add iOS Provisioning Profile—Name This Profile and Generate.
Note
Be descriptive with your provisioning profile name; these names tend to get confusing in Xcode when you have a lot of them. One approach is to use the app name and environment in the name, such as “ICF Shout Out Development.”
When the provisioning profile has been created, a download page will be presented, as shown in Figure 9.16.
Figure 9.16 iOS Provisioning Profile: Your Provisioning Profile Is Ready.
Click Download to get a copy of the provisioning profile. Double-click the profile after it is downloaded and it will be automatically installed and available in Xcode. One last step remains to make sure that the app is using the new provisioning profile. In Xcode, edit the Build Settings for your project (not the target). Find the section titled Code Signing, specifically Code Signing Identity. For Debug, choose the provisioning profile just created and downloaded, as shown in Figure 9.17.
Figure 9.17 Xcode Project Build Settings: Code Signing Identity.
When that step is complete, you have completed all the configuration steps necessary on the app side to receive push notifications. Now you are ready to write some code.
Custom Sound Preparation
One detail that can really distinguish receipt of your push notification is a custom sound. iOS will play any sound less than 30 seconds in length that you specify if it is available in your app bundle. You can create a custom sound in GarageBand and export the sound. It is worth exporting under each of the compression options to see what sounds good while meeting your size requirements (see Figure 9.18).
Figure 9.18 GarageBand: export song settings.
Now that you have a sound file, it will need to be converted to Core Audio format in order for your app to use it. Apple provides a command-line tool called afconvert that is up to the job. Open a Terminal session, navigate to the directory where the audio file is, and issue this command to convert your audio file to Core Audio format:
$ afconvert -f -caff -d ima4 shout_out.m4a shout_out.caf
This command will convert the shout_out.m4a file to ima4 format (which is a compressed format that works well on the device) and package it in a Core Audio–formatted sound file. When that process is complete, copy your new Core Audio format sound file into your Xcode project, and when it is specified in a notification, it will play.
Registering for Remote Notifications
To enable the ShoutOut app to receive push notifications, the app needs to register with the APNs to receive push notifications. The app can be customized to register for push notifications at any point that makes sense, when the user has a good idea what value push notifications will provide from the app. For this example, however, the sample app will register with the APNs right away in the app delegate, the applicationDidFinishLaunchingWithOptions method:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UIApplication sharedApplication]
registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeSound)];
}
Notice that you can specify how the user should be alerted when a push notification is received, including updating the application badge, presenting an alert, and playing a sound. The registerForRemoteNotificationTypes: method will actually call the APNs and get a token to identify the device. Two delegate methods need to be implemented to handle receipt of that token, or an error in registering with APNs:
- (void)application:(UIApplication *)app
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
{
[self setPushToken:devToken];
}
(void)application:(UIApplication *)app
didFailToRegisterForRemoteNotificationsWithError:(NSError *)err
{
NSLog(@"Error in registration. Error: %@", err);
}
If the registration is successful, the token will be returned to the app in NSData format. For ShoutOut, we will just temporarily store the token until we are ready to send it to our server. One approach would be to just perform this registration one time and store the token, and then skip the registration if it has already been completed. Apple recommends that you perform the registration every time, however, since the user might have switched devices and might need a new token. If there are specific failure actions that need to take place, they can be specified in thedidFailToRegisterForRemoteNotificationsWithError: method. For the purposes of the sample app, just log the failure.
At this point, ShoutOut is ready to receive remote push notifications.
Scheduling Local Notifications
For local notifications, no additional setup is required for the app. In ShoutOut, you will use a local notification to be able to schedule a reminder. In ICFMainViewController, there is a method that gets called when the user hits the Set Reminder button:
- (IBAction)setReminder:(id)sender
{
NSDate *now = [NSDate date];
UILocalNotification *reminderNotification = [[UILocalNotification alloc] init];
[reminderNotification setFireDate:[now dateByAddingTimeInterval:60]];
[reminderNotification setTimeZone:[NSTimeZone defaultTimeZone]];
[reminderNotification setAlertBody:@"Don't forget to Shout Out!"];
[reminderNotification setAlertAction:@"Shout Now"];
[reminderNotification setSoundName:UILocalNotificationDefaultSoundName];
[reminderNotification setApplicationIconBadgeNumber:1];
[[UIApplication sharedApplication]
scheduleLocalNotification:reminderNotification];
[reminderNotification release];
UIAlertView *successAlert = [[UIAlertView alloc]
initWithTitle:@"Reminder"
message:@"Your Reminder has been Scheduled"
delegate:nil
cancelButtonTitle:@"OK Thanks!"
otherButtonTitles:nil];
[successAlert show];
[successAlert release];
}
To create a local notification, create an instance of UILocalNotification. Specify the fire date for the notification. It also is generally a good idea to specify a time zone so that if the user is traveling, he will receive the reminder at the correct time. To make it easy to see, just set the fire date to 60 seconds from now. Then set how the user will receive the notification, including specifying alert text, setting whether a sound (or a specific sound) should be played, and updating the application badge. Finally schedule the local notification. Do not forget to release it when complete. To see it in action, run the app, hit Set Reminder, and then close the app. In a minute, you will see an alert with your custom text, sound, and alert badge.
Note
Local notifications can be tested in the simulator, but remote push notifications cannot.
Receiving Notifications
When your device receives a notification, either local or remote, the device will check whether your app is currently active and in the foreground. If not, the parameters included guide the device to play a sound, display an alert, or update the badge on your app icon. If an alert is displayed, the user will have the opportunity to dismiss the alert or to follow the alert into the app.
If the user chooses to go into the app, then either the app delegate’s appDidFinishLaunchingWithOptions: method is called if the app is in a terminated state and is launching, or a delegate method will be called when the app is brought to the foreground. The same delegate method will be called if the app happens to be in the foreground when the notification is received.
If the app was launched as a result of tapping on a notification, the notification payload will be present in the launch options passed to the appDidFinishLaunchingWithOptions: method, and can be used to drive any desired custom functionality, like navigating to a view specific to the navigation or displaying a message.
NSDictionary *notif = [launchOptions
objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (notif) {
//custom logic here using notification info dictionary
}
There are two delegate methods for receiving notifications, one for local and one for remote:
-(void)application:(UIApplication *)application
didReceiveLocalNotification:(UILocalNotification *)notif
{
application.applicationIconBadgeNumber = 0;
if ([application applicationState] == UIApplicationStateActive) {
NSLog(@"Received local notification - app active");
} else {
NSLog(@"Received local notification - from background");
}
}
-(void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
{
application.applicationIconBadgeNumber = 0;
if ([application applicationState] == UIApplicationStateActive) {
NSLog(@"Received remote notification - app active");
} else {
NSLog(@"Received remote notification - from background");
}
}
The local notification delegate method receives the local notification, and the remote notification delegate receives a dictionary with the notification information. In both cases the application parameter can be inspected to determine the state of the app when the notification was received. Then the response to the notification can be customized depending on whether the app is currently active or whether the app was awakened from the background.
Push Notification Server
Because the app is prepared to receive notifications, you can set up the server to send push notifications. You can send push notifications via the APNs from any type of server that can communicate over a secure TCP socket connection (an SSL stack is required). Apple requires that your server maintain a persistent connection while sending push notification requests to APNs to avoid the overhead of establishing connections. For our sample app, we will use a Ruby on Rails server since it is quick to set up, and there is a gem (or prebuilt module) available to automate communication with APNs.
Note
If you want more detail on communication with APNs, look at Apple’s documentation at https://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW1.
Basic Rails Setup
To set up your Ruby on Rails server, you will need the following components on your system:
Ruby version 1.9.2 or higher
Rails 3.0 or higher
SQLite 3.0 or higher
There are plenty of resources on the Web to help install those components. You will need access to a wireless network so that your iOS device can communicate with your server system. This example assumes that your server system is running Mac OS X 10.7 with Bonjour networking for the server URL; if that is not the case, you can use an IP address or another technique for your server URL.
To start the server project, just issue the new project command:
$ rails new icf_message_board_server
That will create the project in a new directory called icf_message_board_server. Switch to the new directory, and run the following:
$ rails server
Then in your Web browser, visit http://0.0.0.0:3000. You will see the standard Rails welcome page (see Figure 9.19). Note that any additional commands or path references in this section are relative to this project directory, unless otherwise noted.
Figure 9.19 Ruby on Rails welcome screen.
Add Support for Devices and Shouts
Now that you have the basic project set up, you need to set up objects and controllers that can handle the two basic functions: capturing device tokens and receiving shouts. Rails has a function called a “scaffold” that will build out a model object, a controller, and basic views; you just need to specify the name of your object, and what attributes and data types you want. Rails automatically supports the following data types:
:binary :decimal :string
:boolean :float :text
:date :integer :time
:datetime :primary_key :timestamp
First, set up your Shout object:
$ rails generate scaffold Shout name:string shout_date:date shout_message:string
The Shout object will have a name, a date, and a message. Now set up your Device object:
$ rails generate scaffold Device name:string token:string
The Device object will be able to capture a username and a device token. Each time you create a new object or make changes to attributes for an object, Rails creates a database migration, allowing you to apply and roll back changes. Run the migrate command to update the database:
$ rake db:migrate
Now that the objects are set up, you need to change the project’s routing so that the list of shouts will be displayed instead of the default Rails welcome screen. In the config / routes.rb file, add a line at the beginning:
root :to => "shouts#index"
Then delete the index.html file in the public directory. Reload http://0.0.0.0:3000 in your browser, and you will see the Shouts home screen (see Figure 9.20).
Figure 9.20 Shouts home screen.
The default index screen for a Rails model object includes links to add new instances of that object, and to edit and delete instances that are displayed. Since you will be displaying this view on the device and do not want the user modifying the shout information, you need to remove these capabilities from the view. In app/views/shouts, edit the index.html.erb file to match the following code example:
<h1>Shout Outs</h1>
<table>
<tr>
<th>Name</th>
<th>Shout date</th>
<th>Shout message</th>
</tr>
<% @shouts.each do |shout| %>
<tr>
<td><%= shout.name %></td>
<td><%= shout.shout_date %></td>
<td><%= shout.shout_message %></td>
</tr>
<% end %>
</table>
The basic infrastructure is in place to handle receiving device information and shouts, and to display shouts in a Web view. Next you need to install support to send push notifications to APNs. To do that, you will install the apn_on_rails gem. First, add this line to the Gemfile:
gem 'apn_on_rails', :git => 'https://github.com/natescherer/apn_on_rails.git', :branch => 'rails3'
Install the gem:
$ bundle install
Build and run the database migrations needed by the gem:
$ rails g apn_migrations
$ rake db:migrate
The application needs to be aware of the gem; add a require statement in the config/environment.rb file:
require 'apn_on_rails'
To handle the rake tasks needed by the gem, add this to the rakefile:
begin
require 'apn_on_rails_tasks'
rescue MissingSourceFile => e
puts e.message
end
One last thing and then the server is ready: You need to add the SSL certificate to the project that you created earlier in the chapter. Open Keychain Access, and locate the certificate and key illustrated in Figure 9.10. Select them both, and choose File, Export Items. Save them asShoutOutSSLCert.p12 (you can use any name with your own p12 files). For communication with APNs, the certificate and key need to be in PEM format, so issue the following openssl command to convert them (note that it is wrapped, it is not two separate commands):
$ openssl pkcs12 -in ShoutOutSSLCert.p12 -out apple_push_notification_development.pem -nodes -clcerts
Move the apple_push_notification_development.pem file to the project config directory, and the project will be ready to send push notifications. Note that the filename must be apple_push_notification_development.pem for the gem to find it, unless you modify the gem’s configuration.
Device Controller
You have to make a few small modifications to the default controller for devices to make sure that it will work for our purposes. In app/controllers/devices_controller.rb, you need to update the create method. First, you need to reformat the token data sent to us from the device, because it will contain chevron brackets ("<" and ">"). This approach will just remove the brackets from the data.
deviceInfo = params[:device]
deviceInfo[:token] = deviceInfo[:token].sub("<","")
deviceInfo[:token] = deviceInfo[:token].sub(">","")
Next, the create method built by Rails assumes that you will create a new record only when a POST is received. The logic needs to be able to handle new devices, and updates to existing devices.
@device = Device.find_by_token(deviceInfo[:token])
if @device
@device.update_attributes(deviceInfo)
else
@device = Device.new(params[:device])
end
This logic either will create a new record if there is not already one for the token, or will update an existing record.
Shout Controller
The app should display the most recent shouts at the top of the list. To do this, you need to change the sort order of shouts in the default index method in app/controllers/shouts_controller.rb:
@shouts = Shout.all.sort_by(&:shout_date).reverse
Next, you need to modify the create method to send a push notification when a new shout has been created. Add this line after you have created the new shout object:
sendPush (@shout.name + ' just shouted ' + @shout.shout_message)
This implies a new method in the controller class called sendPush. This method looks like the following:
def sendPush (message)
deviceList = Device.all
deviceList.each do |sendItem|
device = APN::Device.find_or_create_by_token(sendItem.token)
notification = APN::Notification.new
notification.device = device
notification.badge = 1
notification.sound = "shout_out.caf"
notification.alert = message
notification.save
end
end
There are a couple of items to take note of here. The first is that you are creating a list of devices to be recipients of the push notifications:
deviceList = Device.all
Then, you are iterating over that list of devices, and creating a push notification for each one. To create a push notification, you first need to create a Device object for the APN:
device = APN::Device.find_or_create_by_token(sendItem.token)
This creates a device record for the APN gem to use. Next, you can create the notification:
notification = APN::Notification.new
notification.device = device
notification.badge = 1
notification.sound = "shout_out.caf"
notification.sound = "default"
notification.alert = message
notification.save
You assign the APN device we just created to the new notification object, set the badge, sound, and alert parameters, and save the notification. After you have completed iterating over the list of devices, you will have a stack of notifications ready to send.
Now that the server is ready, you can modify the iOS project so that it can talk to the server.
Tying It All Together
Return to the iOS project. The first thing you need to do is update the URL for the server, so your device knows where to make the network calls. There is an NSString constant called kShoutOutServerURLString in the ShoutOut-Prefix.pch file to identify the server on the local network:
#define kShoutOutServerURLString @"http://twiki.local:3000"
Replace twiki in that address with the name of your computer that is running the Rails server—if it has Bonjour networking that address will work.
Note
Check to be sure your computer and wireless network are not set up to block port 3000.
You need to add logic to tell the server about the user’s device. In the ICFMainViewController, there is a method called showInfo: that is called when the View and Shout! button is pressed. In this method, you will first check to see whether the user has entered a name—if not, an alert view asking for a name will be displayed.
if ([[userNameTextField text] length] > 0)
{
...
} else
{
UIAlertView *successAlert = [[UIAlertView alloc]
initWithTitle:@"Shout Out"
message:@"Please enter your name"
delegate:nil
cancelButtonTitle:@"OK Will Do!"
otherButtonTitles:nil];
[successAlert show];
[successAlert release];
}
If the user provides a name, you will perform an HTTP POST operation on the server with the name and the token you received when you completed the push notification registration. The first step is to create a dictionary with the post parameters (name and token. token is stored in the app delegate after registering the device for push notifications):
ICFAppDelegate *appDelegate =
(ICFAppDelegate *)[[UIApplication sharedApplication] delegate];
NSDictionary *postDictionary =
@{@"device[token]":[appDelegate pushToken],
@"device[name]":[userNameTextField text]};
After you have the dictionary set up, you can call the post method, specifying the path, post parameters, and blocks of code for success and failure of the post.
Note
To make our network calls simple and easy to read, we are using the AFNetworking open source library, written by the folks at Gowalla. It is available at https://github.com/AFNetworking/AFNetworking.
AFHTTPClient *httpClient = [AFHTTPClient clientWithBaseURL:
[NSURL URLWithString:kShoutOutServerURLString]];
[httpClient postPath:@"/devices" parameters:postDictionary
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
ICFFlipsideViewController *controller =
[[[ICFFlipsideViewController alloc]
initWithNibName:@"ICFFlipsideViewController" bundle:nil]
autorelease];
[controller setDelegate:self];
[controller setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[controller setShoutName:[self.userNameTextField text]];
[self presentViewController:controller
animated:YES
completion:nil];
NSLog(@"Device has been successfully logged on server");
[activityView setHidden:YES];
[activityIndicator stopAnimating];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(@"Device setup on server failed: %@",error);
[activityView setHidden:YES];
[activityIndicator stopAnimating];
}];
If the POST operation fails, log an error and turn off the activity view. If the POST completes, display the ICFFlipsideViewController, which will show shouts already posted, and allow the user to post a shout.
When you display the ICFFlipsideViewController, the first thing you need to do is pull down existing shouts and display them in the Web view. In the viewWillAppear method for that controller, add the following:
NSURLRequest *shoutListURLRequest = [NSURLRequest requestWithURL:
[NSURL URLWithString:kShoutOutServerURLString]];
[self.shoutsWebView loadRequest:shoutListURLRequest];
Now, add support for posting a shout. There is a simple text field to enter the shout. In the textFieldShouldReturn: method, first check whether there is any text, and do nothing if not. If there is some text, display the activity indicator, and then build the post dictionary like so:
NSMutableDictionary *postDictionary = [NSMutableDictionary
dictionaryWithCapacity:5];
[postDictionary setObject:[textField text]
forKey:@"shout[shout_message]"];
[postDictionary setObject:[self shoutName]
forKey:@"shout[name]"];
NSDate *today = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy"];
[postDictionary setObject:[dateFormatter stringFromDate:today]
forKey:@"shout[shout_date(1i)]"];
[dateFormatter setDateFormat:@"MM"];
[postDictionary setObject:[dateFormatter stringFromDate:today]
forKey:@"shout[shout_date(2i)]"];
[dateFormatter setDateFormat:@"d"];
[postDictionary setObject:[dateFormatter stringFromDate:today]
forKey:@"shout[shout_date(3i)]"];
[dateFormatter release];
After the post dictionary is ready, you can post the shout to the server, specifying the path, post dictionary, and success and failure blocks.
//set up post request
AFHTTPClient *httpClient = [AFHTTPClient clientWithBaseURL:
[NSURL URLWithString:kShoutOutServerURLString]];
[httpClient postPath:@"/shouts" parameters:postDictionary
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
[self.activityView setHidden:YES];
[self.activityIndicator stopAnimating];
UIAlertView *successAlert = [[UIAlertView alloc]
initWithTitle:@"Shout Out"
message:@"Your Shout Out has been posted!"
delegate:nil
cancelButtonTitle:@"OK Thanks!"
otherButtonTitles:nil];
[successAlert show];
[successAlert release];
NSURLRequest *shoutListURLRequest = [NSURLRequest
requestWithURL:[NSURL URLWithString:kShoutOutServerURLString]];
[self.shoutsWebView loadRequest:shoutListURLRequest];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
[self.activityView setHidden:YES];
[self.activityIndicator stopAnimating];
UIAlertView *failureAlert = [[UIAlertView alloc]
initWithTitle:@"Shout Out"
message:@"Your Shout Out has NOT been posted - ran into an error!"
delegate:nil
cancelButtonTitle:@"Ah Bummer!"
otherButtonTitles:nil];
[failureAlert show];
[failureAlert release];
}];
If the request is successful, we will reload the Web view so that the new shout will be displayed. Now that all of this is in place, run the app on your device (and make sure that your Rails server is running on your computer) and post a shout.
Sending the Push Notifications
Because Apple prefers push notifications to be sent to APNs with a minimum of socket connections (in other words, they will shut you off if you open and close a connection for every notification), you need to be able to send notifications in batches. The apn_on_rails gem handles this task. When you created notifications in the Shout controller, you just saved them. To actually send them, we need to execute a rake task on our server. First, exit the app on your device so that it is not in the foreground, and then issue this command in the root directory of the server project:
$ rake apn:notifications:deliver
The rake command will create a socket connection to APNs, iterate over any unsent notifications, send them to APNs, and then update the “sent date” for each successfully sent notification. You would not want to execute a command-line function manually each time you need to send notifications, so this is a good candidate for a cron job or another automated approach.
After you have run this command, your notification(s) will appear quickly on your device!
Handling APNs Feedback
APNs can provide feedback to each server that connects to it and sends notifications. If any of the device tokens specified in the messages have errors (for example, if the user has deleted the app from her device), then the server should prevent sending future notifications to the disabled devices. Many APNs libraries (including apn_on_rails) have facilities built in to communicate with the feedback endpoint, which can be scheduled to run periodically. After the feedback has been obtained, stored device tokens on the server should be updated or removed to prevent sending additional notifications to them.
Summary
This chapter introduced you to Apple’s method of communicating with apps that are not active and in the foreground: notifications. You learned about the differences between local and remote push notifications. You saw how to set up an app to receive remote push notifications, and how to schedule local notifications. The chapter guided you through how to set up a Ruby on Rails server to communicate with your app and send push notifications to your app via the Apple Push Notification service.
Exercise
1. The sample app will currently send notifications to all devices registered, even the device that has posted the shout. If your device posted the shout, you probably do not need a notification that you posted a shout. Modify the server to prevent sending a notification to the sending device.