iOS > Advanced

iOS 10 migration

As you may already know, iOS 10 brings many changes to the iOS SDK, a lot of these being related to notifications.

Obviously, this has implications on your app's behaviour and how it integrates with Batch. Let's review these changes and what you need to do to ensure a smooth transition:

Xcode 8

Since Batch 1.6 introduces support for native iOS 10 APIs, it now requires Xcode 8. While using the new SDK with Xcode 7 is possible, it is not officially supported. It also means that like Xcode, Batch 1.6 drops support for iOS 6 and iOS 7.

Note: Feel free to ask our support team if you can't migrate to Xcode 8 or drop iOS 7 but are interested in new features.

Swift 3

While Xcode 8 retains Swift 2.2 compatibility with Swift 2.3, Batch aims to be as Swift 3 native as it can. To achieve that goal, some APIs have been renamed to better fit Swift 3's style, and might require some minor fixes to legacy Swift code. Batch retains compatibility with all Swift versions.

Our documentation code snippets also have been updated to Swift 3.

Project configuration

For many projects, Xcode gets confused and disabled the Push Notification capability. If that worked, running the app might output an error about the missing aps-environment entitlement.

To make sure you won't have any surprise, you should open your project file in Xcode, hop on to the capabilities tab and make sure that Push Notifications is ON. If you see a Fix button in this category, press it to generate the missing .entitlements file.

Xcode Capabilities

UserNotifications framework

iOS 10 deprecates all of the previous notifications methods, unifying local and remote notifcations under a single set of methods: say hi to the UserNotifications framework.

The UserNotifications framework brings a lot to the table and allows you to:

  • Show the system-provided alert even if your app is in the foreground
  • Read back notifications in the notification center
    • Update them
    • Dismiss them
  • Ask the system if the user allowed notifications, blocked them or if they haven't been asked yet
  • Get notified of a notification dismissal
  • Trigger local notifications in a more intuitive way
  • Get callbacks at a single place for opens, dismiss, actions for both local and remote notifications

Impressive, right?

Legacy callbacks, the didReceiveRemoteNotifications family, are now deprecated: they are remplaced by UNUserNotificationCenterDelegate.

You now have to make a class that conforms to the UNUserNotificationCenterDelegate protocol, and implement the methods you want. Of course, being a delegate, it is weakly retained, so be sure to store an instance somewhere else. It's cleans up the code nicely:

@objc
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // Handle your notification action/open here
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    let notificationDelegate = NotificationDelegate()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        UNUserNotificationCenter.current().delegate = notificationDelegate
    }
}

If (or rather, when) you'll upgrade to UNUserNotificationCenterDelegate, you'll need to tell Batch about it, or you'll lose nice features, such as the direct open rate, or automatic deeplink handling:

// NotificationDelegate.h

@import Foundation;
@import UserNotifications;

@interface NotificationDelegate : NSObject <UNUserNotificationCenterDelegate>

@end

// NotificationDelegate.m
#import "NotificationDelegate.h"

@import Batch;

@implementation NotificationDelegate

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)())completionHandler {
    [your code]
    [BatchPush handleUserNotificationCenter:center
             didReceiveNotificationResponse:response];
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
    [your code]
    [BatchPush handleUserNotificationCenter:center
                    willPresentNotification:notification
              willShowSystemForegroundAlert:YES];

    // Since you set willShowSystemForegroundAlert to true, you should call completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound)
    // If set to false, you'd call completionHandler(0)
}

@end
@objc
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        [your code]
        BatchPush.handle(center, didReceive: response)
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        [your code]
        BatchPush.handle(userNotificationCenter: center, willPresent: notification, willShowSystemForegroundAlert: true)
        // Since you set willShowSystemForegroundAlert to true, you should call completionHandler([.alert, .sound, .badge])
        // If set to false, you'd call completionHandler([])
    }
}

Note: willShowSystemForegroundAlert should reflect the arguments you call the completion handler with. Batch will use that to detect if it should perform foreground actions, or only perform them when the shown alert will be tapped

Backwards compatibility

There are three essential things to know:

  • Once you set a UNUserNotificationCenterDelegate, legacy callbacks will NOT be called anymore
  • iOS will call legacy callbacks, but doesn't do so in a way that's consistent with iOS 8 and 9.
  • Legacy callbacks are not the same between iOS 10.0 and iOS 10.1/2. As of writing, newer versions of iOS work similarily to iOS 9

We STRONGLY advise updating to the new UserNotifications API to avoid headaches. We've also made a (kinda rough) chart about how the callbacks are called depending on your iOS version here. Note that this chart does not cover cold starts, where things got even more complicated.

Showing foreground notifications

Simply implement the willPresentNotification method of UNUserNotificationCenterDelegate, and tell it to show the notification:

class NotificationDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        BatchPush.handle(userNotificationCenter: center, willPresent: notification, willShowSystemForegroundAlert: true)

        // Tell iOS that we want the notification to behave just like a backgrounded app
        completionHandler([.alert, .badge, .sound])
    }
}

Rich notifications

Notifications have been extended in two ways in iOS 10. You can now modify the content of a push notification before it is displayed (Notification Service Extension), or display your own UI (Notification Content Extension)

Adding rich content to a notification requires you to:

  • Add "mutable-content":1 in the aps object of the push payload
  • Create a notification service extension
  • Send the content URL in the payload
  • Download it in a temporary folder
  • Attach it to the notification

Sounds like a lot of work? Good news: we did the work for you and implemented an that will fetch attachments and then attach them to the notification. We've explained how to integrate that here.
You'll also be glad to know that we added "mutable-content":1 to our default payload.

Notification Content Extensions are custom UIViewControllers that are shown in an expanded push. Even if they don't allow direct user interaction, they open a wide range of possibilities for richer push notifications.