Push Notifications with iOS
I wanted to create a sample iOS application which can handle remote push notifications through APNS. There were a few learnings for me, thought will better document it here. Who knows, will even help me later on.
The code sample is at Github location.
As I understand, the Push Notifications system in iOS exists between the APNS and our iOS primarily.
- The iOS app gives basic app info to the OS, which it combines with some unique device identification and forms both as a request to send to APNS.
- APNS on recognising this, sends back a token to the OS which then flows to the app.
- This token needs to be sent to the server that is dealing with the iOS app so that it can send this token with every push notification it wants to send to the app.
- With this token from the server request, APNS knows that it is a trusted server and knows where to forward the notification.
- The OS then receives it, sends to the right iOS app.
We need to enable each of these steps in our app so that the transaction happens.
- Push certificates are to be enabled for the bundle identifier, quite straightforward and explained here — Apple Reference .
- And this whole setup can be tested only in a device. There is no simulator support as of XCode 7.2.
- Need to enable Push and Background Modes Capabilities in the project settings.
- Can test the server implementation with the wonderfully easy Github setup from here
1) Letting the OS know that our app requires push notification support.
We do this by registering for push notifications and telling the OS what kind of notifications we expect.
For a simple notification, we first create a UIUserNotificationType and define the types needed. We can then create a UIUserNotificationSettings and set these types with nil as category if on iOS 8+ and have our app register to these settings with registerUserNotificationSettings. (see Github page linked above).
We can also create customized notifications or as Apple calls them ‘actionable notifications’. These can present several options to the user with the notification and each can be configured to perform any task within the app — say an option to accept an invitation, a quick text reply (need to set the action.behavior as UIUserNotificationActionBehaviorTextInput iOS 9 only), a permission to do data refresh, adding to calendar etc. These can be configured as instances of UIMutableUserNotificationAction and then added toUIMutableUserNotificationCategory. These are then set to the UIUserNotificationSettings along with the types. We can have different actions tied to different categories. When the server notifies of an event, it can send in a category type too which matches what we define in the app and thus, different kind of notifications can present different options to users. Quite useful! (again, see Github page linked above).
This presents the user an option to configure push notification settings, which is done for a fresh install usually or when user revokes set permissions.
This then fires up the delegate,
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)settings
2) Registering for remote notifications with APNS.
At this point, our app is ready to request the OS for a unique token from APNS. We do this by calling out the registerForRemoteNotifications method on our UIApplication.
Though we can block APNS registration after the first time, Apple recommends we do it at every app launch. So, we check and send device token to server every time. Apparently we don’t incur overhead with this. If the OS has the details, it returns it immediately.
This fires up either the error response delegate,
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
or the success response delegate,
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)token
3) Sending the Device Token to Server.
The didRegisterForRemoteNotificationsWithDeviceToken method returns with a unique token for our app at this instance. This token has to be sent to the server by a custom method. The server retains this token and sends it as an identity token along with the push notification to APNS when needed.
Keep in mind that as of iOS 9.2, a new token is generated for every app re-install or OS change or device change associated with your app. Have to handle the transition gracefully in the server.
Almost all the set up is done by now for the app to receive notifications.
4) Pushing the notification from the server.
APNS requires the notification from the server to follow a specific JSON structure. The main points are listed here.
- Needs a dictionary defined by “aps” which has the basic details necessary for the systems to identify the type of notification payload.
- ‘alert’ is the actual notification message to display to the user
- ‘badge’ is the tag that can take a number to show as application badge
- ‘sound’ is the tag that accompanies the notification. Can include custom audio files too
- ‘content-available’ is the tag used for silent notifications — ones that aren’t alerted to the user but used by the app to know certain events from the server
- ‘category’ is the notification type category
- Custom data can also added as siblings to the “aps” dictionary. These are to be managed by the app on its own.
A couple of sample payloads here -
For silent notifications, need to set the content-available tag value to 1 and can(must) leave out the other tags.
When the server sends these kind of payloads to the APNS along with the device token, when valid, APNS forwards the notifications to the device and the OS forwards it to our app.
This fires the didReceiveRemoteNotification method in case of simple notifications and eitherhandleActionWithIdentifierforRemoteNotification or handleActionWithIdentifierforRemoteNotificationWithResponseInfo for the actionable notifications, both of which we handle next.
5) Handling Remote Notifications.
In case of simple notifications,
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
delegate is fired, and we get the notification sent to us from the userInfo dictionary. We can handle any custom features from here like updating the UI for the user.
In case of non-text-input based actionable notifications,
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)notification completionHandler:(void(^)())completionHandler
delegate is fired. This has the identifier tag of the action/button the user tapped from the notification which can be used to distinguish between enabling different app-specific flows. The completionHandler must be called before exiting this method.
In case of text-input based actionable notifications, available only in iOS 9+, a slight variation of the above delegate is called,
- (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void(^)())completionHandler
This has the responseInfo dictionary which has the UIUserNotificationActionResponseTypedTextKey which holds the value for the text the user typed in the text box presented with the notification. We apparently can handle this for other custom behaviour later on, but I have not yet been able to get this to fire. Unfortunately, Stackoverflow and Apple Dev Forums have not helped me yet on this.
6) Handling active and inactive states of the app.
While the previous UIApplication delegates handle major scenarios quite well, there is a case in which our app is in the inactive state — neither in the foreground or in the background, say when the app is killed forcibly. In such cases, when the notification appears and say we tap on Open, the app has to be launched first by the OS and when it does that, it updates the launchOptions dictionary in the applicationdidFinishLaunchingWithOptions dictionary with the key UIApplicationLaunchOptionsRemoteNotificationKey. This effectively says that the app was launched because of a remote notification. Again, we can handle any custom app-specific behaviour inside this delegate after checking if this key exists. (refer to my Github link for code sample).
That’s basically all there is to implementing notifications as of the day I updated this blog. Hopefully it helps someone understand the basics of it better.
Note: The code sample is at Github location.
Originally published at gtlcodes.blogspot.in.