iOS 13 introduced changes in push notifications handling. This document describes how to migrate to iOS 13 -
This section provides information required for existings apps built with Xcode 10 or below. In order to comply with iOS 13 you must perform the following step and submit your app using Xcode 10 or below. If your app is built with Xcode 11 you must follow the steps noted in the next section.
-
Pass the credential data as the argument directly to the
[TwilioVoice registerWithAccessToken:deviceTokenData:completion:]
method.Swift
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { TwilioVoice.register(withAccessToken: accessToken, deviceToken: credentials.token) { error in if let error = error { NSLog("An error occurred while registering: \(error.localizedDescription)") } else { NSLog("Successfully registered for VoIP push notifications.") } } }
Objective-C
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type { [TwilioVoice registerWithAccessToken:accessToken deviceTokenData:credentials.token completion:^(NSError *error) { if (error) { NSLog(@"An error occurred while registering: %@", [error localizedDescription]); } else { NSLog(@"Successfully registered for VoIP push notifications."); } }];
This section provides migration guides to support the new PushKit push notification policy that iOS 13 and Xcode 11 introduced.
This new policy mandates that Apps built with Xcode 11 and running on iOS 13, which receive VoIP push notifications, must now report all PushKit push notifications to CallKit. Failure to do so will result in iOS 13 terminating the App and barring any further PushKit push notifications. You can read more about this policy and breaking changes here.
The SDK now handles incoming call cancellations internally. The “cancel” push notification is no longer required or supported by new releases of the SDK.
If your App supports incoming calls, you MUST perform the following steps to comply with the new policy:
-
Upgrade to Twilio Voice iOS SDK to 5.x
-
Your app must initialize
PKPushRegistry
with PushKit push type VoIP at the launch time on iOS 13. As mentioned in the PushKit guidelines, the system can’t deliver push notifications to your app until you create a PKPushRegistry object for VoIP push type and set the delegate. If your app delays the initialization ofPKPushRegistry
, your app may receive outdated PushKit push notifications, and if your app decides not to report the received outdated push notifications to CallKit, iOS 13 may terminate your app. -
Report the call to CallKit. Refer to this example for how to report the call to CallKit.
-
You must register via
[TwilioVoice registerWithAccessToken:deviceTokenData:completion:]
when your App starts. This ensures that your app no longer receives "cancel" push notifications. A "call" push notification, when passed to[TwilioVoice handleNotification:delegate:delegateQueue:]
, will return aTVOCallInvite
object to you synchronously via the[TVONotificationDelegate callInviteReceived:]
method when[TwilioVoice handleNotification:delegate:delegateQueue:]
is called. ATVOCancelledCallInvite
will be raised asynchronously via[TVONotificationDelegate cancelledCallInviteReceived:error:]
if any of the following events occur:- The call is prematurely disconnected by the caller.
- The callee does not accept or reject the call in approximately 30 seconds.
- The Voice SDK is unable to establish a connection to Twilio.
You must retain the
TVOCallInvite
to be notified of a cancellation via[TVONotificationDelegate cancelledCallInviteReceived:error:]
. ATVOCancelledCallInvite
will not be raised if theTVOCallInvite
is accepted or rejected.Failure to register with the new release of the SDK may result in app terminations since "cancel" push notifications will continue to be sent to your application and will not comply with the new PushKit push notification policy. If a "cancel" push notification is received, the
[TwilioVoice handleNotification:delegate:delegateQueue:]
method will now returnfalse
.To register with the new SDK when the app is launched:
Swift
// AppDelegate.swift class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { ... self.setupPushRegistry() ... } func setupPushRegistry() { var voipRegistry: PKPushRegistry voipRegistry.delegate = self voipRegistry.desiredPushTypes = Set([PKPushType.voIP]) } func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { let accessToken = fetchAccessToken() TwilioVoice.register(withAccessToken: accessToken, deviceToken: credentials.token) { (error) in ... } } }
Objective-C
// AppDelegate.m @interface AppDelegate () <PKPushRegistryDelegate> @property (nonatomic, strong) PKPushRegistry *voipRegistry; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [self setupPushRegistry]; ... } - (void)setupPushRegistry { self.voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; self.voipRegistry.delegate = self; self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; } #pragma mark - PKPushRegistryDelegate - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type { NSString *accessToken = [self fetchAccessToken]; [TwilioVoice registerWithAccessToken:accessToken deviceTokenData:credentials.token completion:^(NSError *error) { ... }]; } @end
Please note that if the app is updated but never launched to perform the registration, the mobile client will still receive "cancel" notifications, which could cause the app terminated by iOS if the VoIP push notification is not reported to CallKit as a new incoming call. To workaround and avoid app being terminated on iOS 13, upon receiving a "cancel" notification you can report a dummy incoming call to CallKit and then end it on the next tick:
Swift
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { if (payload.dictionaryPayload["twi_message_type"] as! String == "twilio.voice.cancel") { let callHandle = CXHandle(type: .generic, value: "alice") let callUpdate = CXCallUpdate() callUpdate.remoteHandle = callHandle callUpdate.supportsDTMF = true callUpdate.supportsHolding = true callUpdate.supportsGrouping = false callUpdate.supportsUngrouping = false callUpdate.hasVideo = false let uuid = UUID() callKitProvider.reportNewIncomingCall(with: uuid, update: callUpdate) { error in ... } DispatchQueue.main.async { let endCallAction = CXEndCallAction(call: uuid) let transaction = CXTransaction(action: endCallAction) callKitCallController.request(transaction) { error in ... } } return } }
Objective-C
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { if ([payload.dictionaryPayload[@"twi_message_type"] isEqualToString:@"twilio.voice.cancel"]) { CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:@"alice"]; CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; callUpdate.remoteHandle = callHandle; callUpdate.supportsDTMF = YES; callUpdate.supportsHolding = YES; callUpdate.supportsGrouping = NO; callUpdate.supportsUngrouping = NO; callUpdate.hasVideo = NO; NSUUID *uuid = [NSUUID UUID]; [self.callKitProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError *error) { ... }]; dispatch_async(dispatch_get_main_queue(), ^{ CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid]; CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction]; [self.callKitCallController requestTransaction:transaction completion:^(NSError *error) { ... }]; }); return; } }
-
If you were previously toggling
enableInsights
or specifying aregion
viaTVOCallOptions
, you must now set theinsights
andregion
property on theTwilioVoice
class. You must do so before[TwilioVoice connectWithAccessToken:delegate:]
or[TwilioVoice handleNotification:delegate:delegateQueue:]
is called.
You can reference the 5.x quickstart for obj-c and swift when migrating your application.
A summary of the API changes and new Insights events can be found in the 5.0.0 changelog.
If your App supports incoming calls, you MUST perform the following steps to comply with the new policy:
-
Upgrade to Twilio Voice iOS SDK to 2.1.0
-
Your app must initialize
PKPushRegistry
with PushKit push type VoIP at the launch time on iOS 13. As mentioned in the PushKit guidelines, the system can’t deliver push notifications to your app until you create a PKPushRegistry object for VoIP push type and set the delegate. If your app delays the initialization ofPKPushRegistry
, your app may receive outdated PushKit push notifications, and if your app decides not to report the received outdated push notifications to CallKit, iOS 13 may terminate your app. -
Report the call to CallKit. Refer to this example for how to report the call to CallKit.
-
Update how you decode the PushKit device token
Swift
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { ... let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined() ... }
Objective-C
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type { ... const char *tokenBytes = [credentials.token bytes]; NSMutableString *deviceTokenString = [NSMutableString string]; for (NSUInteger i = 0; i < [credentials.token length]; ++i) { [deviceTokenString appendFormat:@"%02.2hhx", tokenBytes[i]]; } ... }
Not updating your App's PushKit device token parsing logic will result in the following error message when calling the
[TwilioVoice registerWithAccessToken:deviceToken:completion:]
method:Error Domain=com.twilio.voice.error Code=31301 "Http status: 400. Unexpected registration response." UserInfo={NSLocalizedDescription=Http status: 400. Unexpected registration response.}
-
You must register via
[TwilioVoice registerWithAccessToken:deviceToken:completion:]
when your App starts. This ensures that your app no longer receives “cancel” push notifications. A "call" push notification, when passed to[TwilioVoice handleNotification:delegate:]
, will return aTVOCallInvite
object to you synchronously via the[TVONotificationDelegate callInviteReceived:]
method when[TwilioVoice handleNotification:delegate:]
is called. The SDK will invoke the[TVONotificationDelegate callInviteReceived:]
method asynchronously with aTVOCallInvite
object of stateTVOCallInviteStateCanceled
if any of the following events occur:- The call is prematurely disconnected by the caller.
- The callee does not accept or reject the call in approximately 30 seconds.
- The Voice SDK is unable to establish a connection to Twilio.
The
[TVONotificationDelegate callInviteReceived:]
method will not be raised with aTVOCallInvite
object of stateTVOCallInviteStateCanceled
if theTVOCallInivte
is accepted or rejected.
Failure to register with the new release of the SDK may result in app terminations since "cancel" push notifications will continue to be sent to your application and will not comply with the new PushKit push notification policy. A new error
TVOErrorUnsupportedCancelMessageError (31302)
is raised when a "cancel" push notification is provided to[TwilioVoice handleNotification:delegate:]
via[TVONotificationDelegate notificationError:]
.To register with the new SDK when the app is launched:
Swift
// AppDelegate.swift class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { ... self.setupPushRegistry() ... } func setupPushRegistry() { var voipRegistry: PKPushRegistry voipRegistry.delegate = self voipRegistry.desiredPushTypes = Set([PKPushType.voIP]) } func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) { let accessToken = fetchAccessToken() let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined() TwilioVoice.register(withAccessToken: accessToken, deviceToken: deviceToken) { (error) in ... } } }
Objective-C
// AppDelegate.m @interface AppDelegate () <PKPushRegistryDelegate> @property (nonatomic, strong) PKPushRegistry *voipRegistry; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [self setupPushRegistry]; ... } - (void)setupPushRegistry { self.voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()]; self.voipRegistry.delegate = self; self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP]; } #pragma mark - PKPushRegistryDelegate - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type { const char *tokenBytes = [credentials.token bytes]; NSMutableString *deviceTokenString = [NSMutableString string]; for (NSUInteger i = 0; i < [credentials.token length]; ++i) { [deviceTokenString appendFormat:@"%02.2hhx", tokenBytes[i]]; } NSString *accessToken = [self fetchAccessToken]; [TwilioVoice registerWithAccessToken:accessToken deviceToken:deviceTokenString completion:^(NSError *error) { ... }]; } @end
Please note that if the app is updated but never launched to perform the registration, the mobile client will still receive "cancel" notifications, which could cause the app terminated by iOS if the VoIP push notification is not reported to CallKit as a new incoming call. To workaround and avoid app being terminated on iOS 13, upon receiving a "cancel" notification you can report a dummy incoming call to CallKit and then end it on the next tick:
Swift
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { if (payload.dictionaryPayload["twi_message_type"] as! String == "twilio.voice.cancel") { let callHandle = CXHandle(type: .generic, value: "alice") let callUpdate = CXCallUpdate() callUpdate.remoteHandle = callHandle callUpdate.supportsDTMF = true callUpdate.supportsHolding = true callUpdate.supportsGrouping = false callUpdate.supportsUngrouping = false callUpdate.hasVideo = false let uuid = UUID() callKitProvider.reportNewIncomingCall(with: uuid, update: callUpdate) { error in ... } DispatchQueue.main.async { let endCallAction = CXEndCallAction(call: uuid) let transaction = CXTransaction(action: endCallAction) callKitCallController.request(transaction) { error in ... } } return } }
Objective-C
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { if ([payload.dictionaryPayload[@"twi_message_type"] isEqualToString:@"twilio.voice.cancel"]) { CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:@"alice"]; CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; callUpdate.remoteHandle = callHandle; callUpdate.supportsDTMF = YES; callUpdate.supportsHolding = YES; callUpdate.supportsGrouping = NO; callUpdate.supportsUngrouping = NO; callUpdate.hasVideo = NO; NSUUID *uuid = [NSUUID UUID]; [self.callKitProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError *error) { ... }]; dispatch_async(dispatch_get_main_queue(), ^{ CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid]; CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction]; [self.callKitCallController requestTransaction:transaction completion:^(NSError *error) { ... }]; }); return; } }
-
If you were specifying a region via the
TwilioVoice.h
region property you must now do so before[TwilioVoice call:params:delegate:]
or[TwilioVoice handleNotification:delegate:]
is called.
You can reference the 2.1 quickstart for obj-c and swift when migrating your application.
A summary of the API changes and new Insights events can be found in the 2.1.0 changelog.