Basic Push Notifications in iOS 10 and Swift

Push notifications are messages from a  remote server to your device.  You find them in many types of applications: Messaging apps, notifications from social media platforms, weather, news, and sports reporting. Because this information comes from an outside source, there’s a lot more security and scrutiny needed to the messages delivered. There’s also a large concern to keep the data transmitted small: you can at most transmit 4K of data.

In this lesson, I’ll show you how to set up your application to receive a push notification. I’m going to make some  assumptions here: You are using iOS 10 to do this and the User Notification framework. If you are not, there plenty of other tutorials by others on the older methods.  Secondly, I’m going to keep to the client side of this. I’m not going to talk at all about setting up a server or using a pre made server to send your push notifications. We’ll use an online tool to test our notifications.  I’ll also assume is you have a paid developer account and a phone to test with. Push notifications are one of those few things that need a paid account. Push notifications don’t run on the simulator either, so you’ll need a live phone to test.

With that said, let’s look at how push notifications differ from local notifications.

Local Notifications,  APNs, Tokens and Certificates

If you’ve read my piece on local notifications, local notifications are based on UNNotificationRequest objects added to the  current UNNotificationCenter. Requests have three parts, a unique identifier, a trigger and content. A trigger gives the conditions when the notification appears on a user’s device. The content is the text and attachments that  appear in the notification.  You as a developer set up all of this in code. For review, here’s a simple notification code snippet:

// ---- Make content -------
let content = UNMutableNotificationContent()
content.title = "MakeAppPie Pizza Co."
content.body = "Hello, Pizza!!"
content.categoryIdentifier = "pizza.category"
// ----- Use a time interval trigger of 5 seconds, non repeating       
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5.0, repeats: false)

// ----- Add the trigger and the content to the request
let request = UNNotificationRequest(identifier: "pizza", content: content, trigger: trigger)

// Add the request to the current notification center, and notify the developer of errors in the closure. 
UNUserNotificationCenter.current().add(request){
     (error) in
     if error != nil{
         print ("Add notification error: \(error?.localizedDescription)")
     }
}

In a remote notification,  all of that is unnecessary.  There’s a server somewhere pushing data towards your device. However between a user’s device and the server is the Apple Push Notifications Service or APNs. To simplify APNs, this service takes the server’s (or as Apple refers to it provider’s) data, checks if it is legitimate data from a legitimate source and sends a  payload  from APNs to the specific device registered to receive the push notification.

APNs coordinates security and device identity through a certificate key and a token. You as a developer register your app with apple and get an app  certificate, an encoded file that identifies your app as a legitimate app to send notifications to. When you run the application, a token gets generated to identify this specific device to APNs.  The developer sends this certificate and token to the provider of the push notification.  Once the provider has this information, when the logic of the provider indicates a need to push a notification, it sends the token in a payload with the message to APNs, who knows how to handle it.

You can read the official documentation or check out the WWDC 2016 video on this for more detail, but for our purposes that’s what you need to know.

Start a New Project

We’ll start this as a new project in Xcode. Open a new single-view project named PushPizzaDemo in Swift. Save the file. The Project opens to  the settings file. Under General, Change the display name to Push Pizza. This will set the icons on the phone to a nicer caption.

2017-01-02_06-49-05

Next to the General Tab now selected, You’ll find the Capabilities tab. Click that and you’ll find a series of extra services you can turn on. The second on the list is Push Notifications. Click the switch to On.

2017-01-02_06-54-40

This sets the app ready to use push notifications and informs Apple you are doing so in your App ID.

Scroll down a bit further, and you’ll find the Background Modes switch.  Open up that Capability, turn it on and select Remote Notifications.

2017-01-02_06-59-11

This tells the system to allow background processing for remote notifications.

We’ve set up the permissions in Xcode to use notifications, we have two more places to get permission: in code and with the certificate.

Register for Notifications

In order to use remote notifications, you have to tell the system before doing anything else you want them. For this reason, you’ll be coding remote notifications in the app delegate. Open the AppDelegate.swift file  and under the import UIKit add the notification framework:

import UserNotifications

Like any other notification, you must ask for permission before using them. For this app, I’ll ask for permission for the alert, sound and badge. Before return true in the application:didFinishLaunchingWithOptions: method add this.

UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]){
     (granted,error) in
     if granted{
         application.registerForRemoteNotifications()
     } else {
         print("User Notification permission denied: \(error?.localizedDescription)")
     }
            
}

If you’ve made a local notification before, most of this code should look familiar. You request authorization for an alert sound and badge notification. In the closure, you deal with the results of that request. If not granted, you post a message with an error to the console.You can change this code to your wish, possibly adding an alert if the user refuses. What’s different from local notifications   is if you are granted access. The application.registerForRemoteNotifications() method registers the app to recieve notifications from APNs.

There’s two more methods of the app delegate you’ll need to add, both of which you should find in the auto completion of Xcode. Add the first one with a comment.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        //TODO: Add code here later to deal with tokens.
    }

The application:didRegisterForremoteNotificationsWithDeviceToken: method runs if registration is successful. You’ll send the deviceToken in the argument from here to your provider. We’ll get back to that later, so for now add the comment.
The second method
application:didFailToRegisterForRemoteNotificationsWithError runs if the registration is not successful. That could be for lack of internet connection, lack of certificate, or you are running on the simulator which prohibits remote notifications among others. Add the application:didFailToRegisterForRemoteNotificationsWithError to print an error to the console.

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register for remote notifications: \(error.localizedDescription)")
    }

Get a Certificate Key

Your next step is to get the  SSL certificate. For that you’ll have to head over to your apple developer account at https://developer.apple.com/account. As I said earlier, you need a paid account to do the next step. Select the Certificates, Identifiers & Profiles button if it appears.
2017-01-02_07-34-40

Under certificates, Select the APNs Auth Key

2017-01-02_14-55-00

You’ll get a screen like this:

2017-01-02_07-50-25

Click the Certificate Signing Request link. It will ask you for an app ID you want to use. Select the PushPizzaDemo:

2017-01-02_07-51-38

Your next step is creating a certificate request. In you Mac’s applications folder find the Utilities folder. In there, run Keychain Access.

2017-01-03_08-10-00

From the top menu of Keychain Access, select Certificate Assistant> Request a Certificate From a Certificate Authority…

2017-01-02_07-54-47

The certificate assistant appears. Use your email address of your developer id for User Email Address.  Add your name to the common name. Make sure to click the radio button Saved to disk.

2017-01-02_07-55-33

Your file will save to disk, I save mine in downloads.

2017-01-02_07-56-16

Once you have that CSR, go back to the certificate request. choose the file you just downloaded .

2017-01-02_07-57-08

Your certificate will generate and will be ready for download.

2017-01-02_07-58-19

Download it and then double click it to add it to your keychain. Once there go back to keychain access and find the entry for it. open the entry to see the private key.

2017-01-02_14-52-08

You only need the key for what’s ahead. Right click and export the private key.

2017-01-02_14-52-23

For simplicity, I left the password blank, and just pressed OK. For security, add a password.

2017-01-02_14-34-52

Keychain Access will ask you if you really want to do this

2017-01-02_14-35-11

Select Allow, and save the exported file somewhere you can find it.

Back in the developer accounts,  Click App ID’s. Find the XC Comm PushPizza Demo entry and click it. All of the application services appear.

2017-01-03_05-33-51

Toward the bottom of this list You’ll find Push Notifications and under that, an edit button. Click the Edit button, the click the push notification entry.

2017-01-02_14-37-56

You’ll see the results of all the hard work in getting your SSL certificate.  You can generate more here if you need them. I used a production certificate for this example since it can be used for both production and development. You could use a sandboxed development one as well. You need a new certificate for each app, so this isa process you’ll be repeating frequently if working with remote notifications.

Get a Token from APNs

To use a remote notification provider, you need a certificate and a token. The app generates the token with information about the device running the application. It asks APNs some questions, and APNs returns the token. While this is an oversimplification, imagine that the SSL certificate is permission to talk to APNs and the token is where the provider wants to send a message from APNs. Back in Xcode, we stubbed a method earlier that gives us that token.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        //TODO: Add code to get token here
    }

The deviceToken is of type Data. While there might be some systems that accept a type Data token, you may find many, including online test platforms, want a string. You’ll need to convert this into a string of 8-bit hexadecimal numbers. Check with the provider’s documentation on what you need.

I wrote a small function to do this conversion. Add this to your AppDelegate:

func tokenString(_ deviceToken:Data) -> String{
//code to make a token string
    let bytes = [UInt8](deviceToken)
    var token = ""
    for byte in bytes{
        token += String(format: "%02x",byte)
     }
     return token
}

This makes an array of 8-bit unsigned integers, then converts each of those integers into a 2-digit hexadecimal number, which gets concatenated to a token string. I can add this to the application:didRegisterForRemoteNotificationsWithDeviceToken: method to print it out on the console.

print("Successful registration. Token is:")
print(tokenString(deviceToken))

You might send it to your provider though a provider specific add-on library or payload. Check the documentation for how to do that. In this example, I’m cutting and pasting this into a website, so sending to the console is what I want.

Run the Code

For the application, we’ve gotten everything put together in code. The app will run, register a device then return a token to the console. That’s all it needs to do. Connect a device (i used an iPad mini)  with an internet connection to Xcode and set the scheme to run the device. Since we set nothing up on the storyboard, the device will be a pretty boring blank screen. First it will ask for permission to use notifications.

2017-01-03_08-20-47

If you allow, it will print in the console something like

Successful registration. Token is:
3d1864107665c4cc4a55130e0249d3f6aab07a4fde3bf91426a45e7f2f13b4e1

Shut down the app in Xcode. On your device, Go to the home screen.

Test a Payload with an Online Tester

You now have a certificate and token. With both you can send a notification. There’s several ad-supported sites which work the same way. I’ll use pushtry.com due to flexibility in certificates. Load the site and you’ll see several fields for the iOS version

2017-01-03_06-34-14

At the top, you add your certificate by clicking the Choose File button. If you used a password  for the certificate place it in the password field. I didn’t for this very insecure example, so I’ll leave it blank. For the token field, cut and paste the token from the console (don’t use mine – it won’t work).

2017-01-03_06-35-59

The bottom fields show if this is a production system or a developer. Using a production certificate you can do either, so I left it as a development mode.  At the very bottom is two radio buttons switching from text to JSON. APNs prefers JSON, so click JSON, which puts a sample payload in for you.

2017-01-03_06-36-29

Press send. if it all works right, you see a message that the notification sent correctly.

2017-01-03_06-53-01

 

From all time messing in the website, your device probably went sleep.  The push notification will wake it up.

img_0009

If it was still awake, you’ll get a banner, and see a badge on the icon:

img_0010

Tap either, and you’ll launch the application into the foreground, which for us is a bank white screen.

A Note about PEM Certificates

You may have noticed that Pushtry.com takes two types of files PEM and p12.  Many systems and test platforms need you to use PEM. I use Pushtry for this reason: for early testing I can skip a step.  However, you will find a place that needs a PEM, and to get that, you’ll need some command line work. Open up a terminal instance by going to applications>Utilities>terminal.

Go to where you saved the certificate. I try to download these to the desktop to make this step easier.

Go to the desktop, or wherever you saved the certificate.

cd desktop

Run this command, which converts the file.

openssl pkcs12 -in PushPIzzaCertificates.p12 -out pushpizzacert.pem -nodes

Now you can use the PEM. file as well, which you’ll find on your desktop.

Payloads are Your Content

Much of what we just did replaces the trigger in a local notification. Content for a notification is found in the payload. Going back to the testing platform, you’ll find this:

{"aps":{"alert":"Enter your message","badge":1,"sound":"default"}}

Ideally your JSON file should look like this. You only have 4K for your payload so wasting it on spaces is frowned on. When sending payloads avoid whitespace. However, they are hard to read this way. It looks better like this:

{
     "aps":{
            "alert":"Enter your message",
            "badge":1,
            "sound":"default"
     }
}

The aps is a JSON dictionary with entries that describe your content. The alert entry is can be a string like it is here, or a dictionary describing the content of the alert that shows on your device. The badge give the number to show on the badge icon. The sound plays the default sound. You can modify this payload to change the content displayed in the alert. As the alert can be both a dictionary or a string you can add more to it. Change the payload to this:

{
     "aps":{
            "alert":{
                    "title":"Push Pizza Co.",
                    "body":"Your pizza is ready!"
             },
                "badge":42,
                "sound":"default"
     }
}

This will add a title and a message about your pizza being ready. It will also change the badge to 42.
Without spaces that’s this

{"aps":{"alert":{"title":"Push Pizza Co.","body":"Your pizza is ready!"},"badge":42,"sound":"default"}}

Change the payload in the tester to the above code and send the notification. You’ll get this

img_0011

The notification appears with the title and body. The badge appears with the number 42.

There’s a lot more you can do with the payload, including categories and setting up action buttons. We’ll discuss all that in the next lesson.

 

The Whole Code

All the code this time was in the app delegate, so I’m only posting a trimmed version with the methods we use. Since there’s so many external parts to push notification, there’s no downloads this time.

//
//  AppDelegate.swift
//  PushPizzaDemo
//
//  Created by Steven Lipton on 1/2/17.
//  Copyright © 2017 Steven Lipton. All rights reserved.
//

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

// Check if you have permission to use notifications. 
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge])
        {(granted,error) in
            if granted{
                application.registerForRemoteNotifications()
            } else {
                print("User Notification permission denied: \(error?.localizedDescription)")
            }
            
        }
        return true
    }
         
 //code to make a token string  
    func tokenString(_ deviceToken:Data) -> String{
        let bytes = [UInt8](deviceToken)
        var token = ""
        for byte in bytes{
            token += String(format: "%02x",byte)
        }
        return token
    }
// Successful registration and you have a token. Send the token to your provider, in this case the console for cut and paste. 
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
      
        print("Successful registration. Token is:")
        print(tokenString(deviceToken))
    }
// Failed registration. Explain why.     
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register for remote notifications: \(error.localizedDescription)")
    }

}


 

5 thoughts on “Basic Push Notifications in iOS 10 and Swift”

  1. You should export the certificate rather than the private key from the Keychain. So, “Right click and export the private key.” should be corrected to “Right click and export the certificate for push service”. Or no notification will be received on device even if the sending seems to be successful from the online test tools.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s