Push Notifications on iOS

Push notifications are an important part of the mobile experience. Users have grown accustomed to having push notifications be apart of virtually every app that they use. The iOS Programmable Chat SDK is built to have push notifications integrated into it.

Enable push notifications for your Service instance

IMPORTANT: The default enabled flag for new Service instances for all Push Notifications is false. This means Push will be disabled until you explicitly enable it. Follow this guide to do so.

Note: You will need to configure the sound setting value for each push notification type you want the sound payload parameter to present for, with required value. More information can be found in the Push Notification Configuration Guide.

Managing your push credentials

Managing your push credentials will be necessary, as your device token is required for the Chat SDK to be able to send any notifications through APNS. Let's go through the process of managing your push credentials.

The AppDelegate class contains a series of application lifecycle methods. Many important events that occur like your app moving to the background or foreground have event listeners in this class.

When working with push notifications in your iOS application, it is quite likely you will find yourself needing to process push registrations or received events prior to your Chat client being initialized. For this reason, we recommend you create a spot to store any registrations or push messages your application receives prior to the client being fully initialized. The best option here is to store these in a helper class your application delegate can obtain a reference to so that your Chat client can process these values post-initialization if necessary or real-time otherwise. If you are doing a quick proof of concept, you could even define these on the application delegate itself but we recommend you refrain from doing this as storing state on the application delegate is not considered a best practice on iOS.

The properties we will assume are defined somewhere accessible to both your application delegate's methods as well as where you initialize your Chat client are:

Loading Code Samples...
Language
@property (nonatomic, strong) NSData *updatedPushToken;
@property (nonatomic, strong) NSDictionary *receivedNotification;
@property (nonatomic, strong) TwilioChatClient *chatClient;
var updatedPushToken: NSData?
var receivedNotification: [NSObject : AnyObject]?
var chatClient: TwilioChatClient?
Chat Push State Variables

Let's implement the following method

Loading Code Samples...
Language
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
    if(notificationSettings.types == UIUserNotificationTypeNone) {
        NSLog(@"Failed to get token, error: Notifications are not allowed");
        if (self.chatClient) {
            [self.chatClient registerWithToken:nil];
        } else {
            self.updatedPushToken = nil;
        }
    } else {
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    }
}
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
    if notificationSettings.types == .None {
        NSLog("Failed to get token, error: Notifications are not allowed")
        if let chatClient = chatClient where chatClient.userInfo != nil {
            chatClient.registerWithToken(nil)
        } else {
            updatedPushToken = nil
        }
    }
    else {
        UIApplication.sharedApplication().registerForRemoteNotifications()
    }
}
User Notification Setttings

This method will register for remote notifications if notifications are allowed. The registerForRemoteNotificationTypes method also 2 delegate methods for success and failure, respectively

Loading Code Samples...
Language
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    if (self.chatClient && self.chatClient.user) {
        [self.chatClient registerWithNotificationToken:deviceToken
                                            completion:^(TCHResult *result) {
                                                if (![result isSuccessful]) {
                                                    // try registration again or verify token
                                                }
                                            }];
    } else {
        self.updatedPushToken = deviceToken;
    }
}

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
    NSLog(@"Failed to get token, error: %@", error);
    self.updatedPushToken = nil;
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
    if let chatClient = chatClient where chatClient.user != nil {
        chatClient.registerWithNotificationToken(deviceToken) { (result) in
          if (!result.isSuccessful()) {
            // try registration again or verify token
          }
        }
    } else {
        updatedPushToken = deviceToken
    }
}

func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
    NSLog("Failed to get token, error: %@", error)
    updatedPushToken = nil
}
Store Registration

We print an error it it fails but if it succeeds, we either update the Chat client directly or save the token for later use.

Provisioning Apple Developer credentials for APN Pushes

We're going to need to export both certificate and private key from Keychain Access:

  1. Start the “Keychain Access” application on your Mac
  2. Pick the "My Certificates" Category in the lefthand sidebar
  3. Right-click "Apple Development iOS Push Services" certificate
  4. In the popup menu choose "Export…"
  5. Save it as "cred.p12" without protecting it with password (leave the password blank)
  6. Extract the certificate from "cred.p12" into a "cert.pem" file
  7. Run the following command in terminal
openssl pkcs12 -in cred.p12 -nokeys -out cert.pem -nodes
  1. Strip anything outside of "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" boundaries
  2. Extract your private key from the "cred.p12" (PKCS#12) into the "key.pem" (PKCS#1) file
  3. Run the following command in terminal
openssl pkcs12 -in cred.p12 -nocerts -out key.pem -nodes

The resulting file should contain "-----BEGIN RSA PRIVATE KEY-----". If the file contains "-----BEGIN PRIVATE KEY-----" and run the following command:

openssl rsa -in key.pem -out key.pem

Strip anything outside of "-----BEGIN RSA PRIVATE KEY-----" and "-----END RSA PRIVATE KEY-----" boundaries and upload your credentials into Twilio Platform

To store your Credential, simply visit your Chat Push Credentials Console and click on the 'Create a Push Credential' button. This console is located here:

Programmable Chat Push Credentials Console

The Credential Sid for your new Credential will be listed in the detail page labelled 'Credential SID'.

You should also ensure you add your credential SID to the Access Token Chat grant for iOS endpoints request tokens. Each of the Twilio Helper Libraries make provision to add the 'push_credential_sid'. Please see the relevant documentation for your preferred Helper Library for the details.

var ipmGrant = new IpMessagingGrant({
    serviceSid: IPMessagingServicesSid,
    endpointId: endpointId,
    pushCredentialSid: APNCredentialSid,
});

Nice! That's all we need to make sure the client has access to your device token!

Integrating Push Notifications

You could keep your face buried in your phone all day waiting for your crush to send you an IP message telling you that they're interested. Or you could turn on push notifications and go about your life, only getting notified once you receive their message. Sounds like a better option, right? Push notifications are a supremely useful tools to keep users up to date with the status of their communication channels. Let's go through the process for integrating push notifications on iOS.

The AppDelegate class contains a series of application lifecycle methods. Many important events that occur like your app moving to the background or foreground have event listeners in this class. One of those is the 'applicationDidFinishLaunchingWithOptions' method

Loading Code Samples...
Language
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
func application(application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
Did Finish Launching

In this method, we're going to want to integrate push notifications for our app like so

Loading Code Samples...
Language
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
       [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
} else {
       [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
        (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)];
}
UIApplication.sharedApplication().registerUserNotificationSettings(
    UIUserNotificationSettings(
        forTypes: [.Sound, .Alert, .Badge],
        categories: nil
    )
)
Notification Types

This conditional checks for the iOS version that the device is running and registers for notifications with the appropriate registration method. That's it! We're now registered for notifications.

Receiving Notifications

Receiving notifications in our app let's us react to whatever event just occured. It can trigger our app to update a view, change a status, or even send data to a server. Whenever the app receives a notification, the method didReceiveRemoteNotification is fired

Loading Code Samples...
Language
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  // If your application supports multiple types of push notifications, you may wish to limit which ones you send to the TwilioChatClient here
  if (self.chatClient && userInfo) {
      // If your reference to the Chat client exists and is initialized, send the notification to it
      [self.chatClient handleNotification:userInfo completion:^(TCHResult *result) {
        if (![result isSuccessful]) {
          // Handling of notification was not successful, retry?
        }
      }];
  } else {
      // Store the notification for later handling
      self.receivedNotification = userInfo;
  }
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
  // If your application supports multiple types of push notifications, you may wish to limit which ones you send to the TwilioChatClient here
  if let chatClient = chatClient where chatClient.userInfo != nil {
      // If your reference to the Chat client exists and is initialized, send the notification to it
      chatClient.handleNotification(userInfo) { (result) in
        if (!result.isSuccessful()) {
          // Handling of notification was not successful, retry?
        }
      }
  } else {
      // Store the notification for later handling
      receivedNotification = userInfo
  }
}
Did Receive Notification

We will pass the notification directly on to the Chat client if it is initialized or store the event for later processing if not.

The userInfo parameter contains the data that the notification passes in from APNS. We can update our Chat client by passing it into the singleton via the receivedNotification method. The manager wraps the Chat client methods that process the notifications appropriately.

Integration upon client startup

Once your Chat client is up and available, you can provide the push token your application received:

Loading Code Samples...
Language
if (self.updatedPushToken) {
    [self.chatClient registerWithNotificatrionToken:self.updatedPushToken
                                         completion:^(TCHResult *result) {
      if (![result isSuccessful]) {
        // try registration again or verify token
      }
    }];
}

if (self.receivedNotification) {
    [self.chatClient handleNotification:self.receivedNotification
                             completion:^(TCHResult *result) {
      if (![result isSuccessful]) {
        // Handling of notification was not successful, retry?
      }
    }];
}
if let updatedPushToken = updatedPushToken {
    chatClient.registerWithToken(updatedPushToken) { (result) in
      if (!result.isSuccessful()) {
        // try registration again or verify token
      }
    }
}

if let receivedNotification = receivedNotification {
    chatClient.handleNotification(receivedNotification) { (result) in
      if (!result.isSuccessful()) {
        // Handling of notification was not successful, retry?
      }
    }
}
Register Notifications

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
@property (nonatomic, strong) NSData *updatedPushToken;
@property (nonatomic, strong) NSDictionary *receivedNotification;
@property (nonatomic, strong) TwilioChatClient *chatClient;
var updatedPushToken: NSData?
var receivedNotification: [NSObject : AnyObject]?
var chatClient: TwilioChatClient?
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
    if(notificationSettings.types == UIUserNotificationTypeNone) {
        NSLog(@"Failed to get token, error: Notifications are not allowed");
        if (self.chatClient) {
            [self.chatClient registerWithToken:nil];
        } else {
            self.updatedPushToken = nil;
        }
    } else {
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    }
}
func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {
    if notificationSettings.types == .None {
        NSLog("Failed to get token, error: Notifications are not allowed")
        if let chatClient = chatClient where chatClient.userInfo != nil {
            chatClient.registerWithToken(nil)
        } else {
            updatedPushToken = nil
        }
    }
    else {
        UIApplication.sharedApplication().registerForRemoteNotifications()
    }
}
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
    if (self.chatClient && self.chatClient.user) {
        [self.chatClient registerWithNotificationToken:deviceToken
                                            completion:^(TCHResult *result) {
                                                if (![result isSuccessful]) {
                                                    // try registration again or verify token
                                                }
                                            }];
    } else {
        self.updatedPushToken = deviceToken;
    }
}

- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
    NSLog(@"Failed to get token, error: %@", error);
    self.updatedPushToken = nil;
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
    if let chatClient = chatClient where chatClient.user != nil {
        chatClient.registerWithNotificationToken(deviceToken) { (result) in
          if (!result.isSuccessful()) {
            // try registration again or verify token
          }
        }
    } else {
        updatedPushToken = deviceToken
    }
}

func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
    NSLog("Failed to get token, error: %@", error)
    updatedPushToken = nil
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
func application(application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
       [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
} else {
       [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
        (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)];
}
UIApplication.sharedApplication().registerUserNotificationSettings(
    UIUserNotificationSettings(
        forTypes: [.Sound, .Alert, .Badge],
        categories: nil
    )
)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  // If your application supports multiple types of push notifications, you may wish to limit which ones you send to the TwilioChatClient here
  if (self.chatClient && userInfo) {
      // If your reference to the Chat client exists and is initialized, send the notification to it
      [self.chatClient handleNotification:userInfo completion:^(TCHResult *result) {
        if (![result isSuccessful]) {
          // Handling of notification was not successful, retry?
        }
      }];
  } else {
      // Store the notification for later handling
      self.receivedNotification = userInfo;
  }
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
  // If your application supports multiple types of push notifications, you may wish to limit which ones you send to the TwilioChatClient here
  if let chatClient = chatClient where chatClient.userInfo != nil {
      // If your reference to the Chat client exists and is initialized, send the notification to it
      chatClient.handleNotification(userInfo) { (result) in
        if (!result.isSuccessful()) {
          // Handling of notification was not successful, retry?
        }
      }
  } else {
      // Store the notification for later handling
      receivedNotification = userInfo
  }
}
if (self.updatedPushToken) {
    [self.chatClient registerWithNotificatrionToken:self.updatedPushToken
                                         completion:^(TCHResult *result) {
      if (![result isSuccessful]) {
        // try registration again or verify token
      }
    }];
}

if (self.receivedNotification) {
    [self.chatClient handleNotification:self.receivedNotification
                             completion:^(TCHResult *result) {
      if (![result isSuccessful]) {
        // Handling of notification was not successful, retry?
      }
    }];
}
if let updatedPushToken = updatedPushToken {
    chatClient.registerWithToken(updatedPushToken) { (result) in
      if (!result.isSuccessful()) {
        // try registration again or verify token
      }
    }
}

if let receivedNotification = receivedNotification {
    chatClient.handleNotification(receivedNotification) { (result) in
      if (!result.isSuccessful()) {
        // Handling of notification was not successful, retry?
      }
    }
}