Push Notifications

Push Notifications are remote notifications - they are all sent to the device by a remote server. It can be a notification about a new message on the social network, notification about some new content, or just a reminder to continue the subscription.

Remote notifications setup in your app is very similar to Local Notifications. Because all notifications will be sent from the server, you just need to ask the user to give you permissions, get device's unique token and send it to your server. Also, you can also tell the app what to do when the notification arrives with your app in the foreground with an application:didReceiveRemoteNotification:.

You can also use Push Notifications to tell the app that server has some new data. In this case you should use application:didReceiveRemoteNotification:fetchCompletionHandler: delegate method. Inside you can process the notification and start downloading new data from the server. fetchCompletionHandler should be called when synchronization will be completed.

To send Push Notifications, you need to be a member of Apple Developer Program. Each time your server sends a push notification, it will need to use a unique certificate.

Let's set up our device first. Since we don't actually have the server, we will just print the token to the console, and then we will send a notification with some Ruby gem. We are going to use Tutorials app for it since superapp does not require push notifications.

Let's add Push Notifications to the table data in the HomeSecreen first:

        def set_table_data
          @data = [
            {
              cells: [
                {
                  title: "Collection Views",
                }, {
                  title: "Map Views",
                }, {
                  title: "Web Views",
                }, {
                  title: "UI Elements",
                }, {
                  title: "Push Notifications",
                }
              ]
            }
          ]
        end

        # ... other HomeScreen code ...

        def tableView(table, didSelectRowAtIndexPath: index)
          if index.row == 0
            open CollectionScreen.new
          elsif index.row == 1
            open MapScreen.new
          elsif index.row == 2
            url = 'http://google.com'.nsurl
            open WebScreen.alloc.initWithURL url, entersReaderIfAvailable: false
          elsif index.row == 3
            open ElementScreen.new
          elsif index.row == 4
            open NotificationScreen.new
          end
        end
      
And now let's create a NotificationScreen. It will have a label that will tell the state of Push Notifications Permissions, the button that will ask the user for permissions, and the button that will print the token to the console. Let's start with layout:
        class NotificationLayout < MotionKit::Layout
          def layout
            add UILabel, :state
            add UIButton, :register
            add UIButton, :token
          end

          def state_style
            constraints do
              center_x.equals(:superview, :center_x)
              top.equals(:superview, :top).plus(90)
            end
          end

          def button_style
            title_color 0x0000ff.uicolor
            background_color 0xffffff.uicolor
          end

          def register_style
            button_style
            title "Register for Push Notifications"

            constraints do
              center_x.equals(:superview, :center_x)
              top.equals(:state, :bottom).plus(20)
            end
          end

          def token_style
            button_style
            title "Print Device's token"

            constraints do
              center_x.equals(:superview, :center_x)
              top.equals(:register, :bottom).plus(20)
            end
          end
        end
      
NotificationScreen code will look similar to this:
        class NotificationScreen < PM::Screen
          title "Push Notifications"

          def load_view
            @layout = NotificationLayout.new
            self.view = @layout.view
          end

          def on_load
            self.view.backgroundColor = 0xffffff.uicolor

            @state = @layout.get(:state)
            update_state

            @layout.get(:register).on_tap do
              register_for_notifications
            end

            @layout.get(:token).on_tap do
              show_token
            end
          end

          def update_state
            @state.text = "Not Registered for Push Notifications"
          end

          def register_for_notifications

          end

          def show_token

          end
        end
      
You can see that we have empty register_for_notifications and show_token methods. We will add their logic a little bit later. Now to finish preparations, let's add application:didReceiveRemoteNotification: to our AppDelegate, so it will always tell us that notification arrived, and printed its alertBody to us:
        def application(app, didReceiveRemoteNotification: notification)
          NSLog "Received Push Notification: #{notification.alertBody}"
        end
      

Now we can fill our empty methods with some code. First we need to figure out whether the user is registered for push notifications or not. You can remember how we have done it for Local Notifications, but Remote Notifications have a simpler way to do it. UIApplication's instance has an isRegisteredForRemoteNotifications property that tells us whether the user has allowed the app to receive Push Notifications or not. Let's add a method that will tell help us to understand if user has registered for push notifications:

        def registered?
          app = UIApplication.sharedApplication
          app.isRegisteredForRemoteNotifications
        end
      
Now we can change update_state method to always update the label correctly. If device is not registered, it will set the label to "Not Registered for Push Notifications", otherwise, it will be set to "Registered for Push Notifications":
        def update_state
          if registered?
            @state.text = "Registered for Push Notifications"
          else
            @state.text = "Not Registered for Push Notifications"
          end
        end
      
register_for_notifications should also check registered? method first. If it returns true, than we will just show an alert that tells the user that he is already registered. Otherwise, we will ask him to give the app permissions to receive Push Notifications.

Do you remember how we asked for permissions for Local Notifications? First we showed a message, and actual result of the dialog was in the AppDelegate's application(app, didRegisterUserNotificationSettings: settings) method. We will use the same way here too. First we will ask the user if we can show notifications at all with the same code that we did use in Local Notifications Chapter. Then inside of the application(app, didRegisterUserNotificationSettings: settings) method, we will check if user allowed us to show him notifications. If notifications allowed, we will ask for a second permission - Remote Notifications permissions, which will call next delegate methods: application(app, didRegisterForRemoteNotificationsWithDeviceToken: token) if registered successfully, and application(app, didFailToRegisterForRemoteNotificationsWithError: error) if failed to register for push notifications for some reason. Let's start with AppDelegate:

        def application(app, didRegisterUserNotificationSettings: settings)
          if settings.types != UIUserNotificationTypeNone
            # user allowed the app to show notifications. Ask for
            # Remote Notifications permissions now:
            app.registerForRemoteNotifications
          end
        end

        def application(app, didRegisterForRemoteNotificationsWithDeviceToken: token)
          # convert token's data to string:
          string = token.description

          # save token:
          NSUserDefaults["push_token"] = string
        end

        def application(app, didFailToRegisterForRemoteNotificationsWithError: error)
          NSLog "Failed to register for push notifications"
          NSLog "#{error}"
        end
      
And updated register_for_notifications method:
        def register_for_notifications
          if registered?
            UIAlertController.alert(self, "Registered!",
              message: "App has already gaven permissions to receive Push Notifications")
            return
          end

          app = UIApplication.sharedApplication
          types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert
          settings = UIUserNotificationSettings.settingsForTypes types, categories: nil
          app.registerUserNotificationSettings settings
        end
      
show_token method from the NotificationScreen should only print the device token if it is registered for push notifications:
        def show_token
          if registered?
            NSLog "Device token: #{NSUserDefaults['push_token']}"
          else
            NSLog "Not registered for push notifications."
          end
        end
      
Let's launch the app. When you try to register for push notifications, you will see an error in your console:
Unfortunately, we can't test Push Notifications in the Simulator, and now we need to generate Certificates and Provisioning Profiles for the app, and Certificates for Push Notifications.

This tutorial won't describe in detail how to get all push certificates you need because there are lots of information on it on the internet. I will just list most important steps here:

Now when you have all certificates downloaded, install them in your Keychain.

To include your provisioning profiles into the app, copy them to app's folder, for example into tutorials_app/certificates. Rename certificates if needed. I usually use names like dev.mobileprovision, adhoc.mobileprovision, and appstore.mobileprovision.

Now we need to let your app know about certificates and provisioning. As usual, all apps settings can be found in Rakefile, so let's open it, and add next code:

        app.development do
          # Development Certificate
          app.codesign_certificate = "iPhone Developer: XXXX (XXXXXXX)"
          app.provisioning_profile = "certificates/dev.mobileprovision"
          app.entitlements["aps-environment"] = "development"
        end

        app.release do
          app.entitlements['get-task-allow'] = false
          app.codesign_certificate = "iPhone Distribution: XXXX (XXXXX)"
          app.provisioning_profile = "certificates/appstore.mobileprovision"
          app.entitlements['beta-reports-active'] = true

          app.seed_id = "XXXXXX"
          app.entitlements['application-identifier'] = app.seed_id + '.' + app.identifier
          app.entitlements['keychain-access-groups'] = [ app.seed_id + '.' + app.identifier ]
          app.entitlements["aps-environment"] = "production"
        end
        
Values for app.codesign_certificate and app.seed_id can be found in OS X Keychain Access app. Select Certificates group on the left bottom, and then find your new certificate. Inside of it, Common Name can be used as app.codesign_certificate and User Id as app.seed_id. When ready, try to launch the app on the device with rake device. In some minutes, your app will be compiled and launched on your device.

To view device logs, you can use motion device-console command. It will help you to understand whether you were able to register for Push Notifications, and will show you your token if so.

This is it on the app side! Now we need to configure a server to send notifications to a given device ID. Since we don't want to set up a server at this moment, we will just use a gem that Ruby on Rails developers use when they set up the server - Houston

Let's install it with gem install houston

Now we need to convert Apple Push Notification Certificate into *.pem file. To do this, open your Keychain Access app, find your Push Notification Certificate, and press a disclosure icon, so Keychain Access will show both certificate and the key:

Select both the certificate and the key, open a context menu with a right-click, and select "Export 2 items". Save it somewhere as cert.p12, and in console navigate to the folder with this file. Then use next command to convert p12 into pem:
        openssl pkcs12 -in cert.p12 -out cert.pem -nodes -clcerts
      

This is it! We are ready to send our first Push Notification. Since we have no server, we will use Houston's command line utility. Navigate to the folder with your cert.pem, and run next command:

        apn push "device_token" -c ./cert.pem -m "Hello from the command line!"
      

Version Control

Let's save the progress with git:

        git add .
        git commit -m "Push Notifications added"
      

Summary

Push Notifications are notifications sent to your device by some server. It is important to know device's token. Otherwise, the server won't know where to send a notification. Token will be generated right after asking for Notifications Permissions. If user allowed the app to show notifications, AppDelegate's method application(app, didRegisterForRemoteNotificationsWithDeviceToken: token) would return the token, that will need to be saved and sent to the server.

To send notifications, you will also need to obtain certificates for Push Notifications and your app on Apple Developer Portal.

Book Index | Next