Awesome
Twilio Voice Objective-C Quickstart for iOS
Deprecation Notice - voice-quickstart-objc repository
This repository has been deprecated and will no longer be maintained. All Objective-C examples have been merged into the voice-quickstart-ios repository and will continue to be maintained there.
NOTE: These sample applications use the Twilio Voice 5.x APIs. For examples using our 2.x APIs, please see the 2.x branch. If you are using SDK 2.x, we highly recommend planning your migration to 5.0 as soon as possible. Support for 2.x will cease 1/1/2020. Until then, SDK 2.x will only receive fixes for critical or security related issues.
Please see our iOS 13 Migration Guide for the latest information on iOS 13.
Get started with Voice on iOS:
- Quickstart - Run the quickstart app
- Migration Guide from 4.x to 5.x - Migrating from 4.x to 5.x
- 4.0 New Features - New features in 4.0
- Migration Guide from 3.x to 4.x - Migrating from 3.x to 4.x
- 3.0 New Features - New features in 3.0
- Migration Guide from 2.x to 3.x - Migrating from 2.X to 3.x
- Access Tokens - Using access tokens
- Managing Audio Interruptions - Managing audio interruptions
- Managing Push Credentials - Managing push credentials
- More Documentation - More documentation related to the Voice iOS SDK
- Issues and Support - Filing issues and general support
Quickstart
To get started with the quickstart application follow these steps. Steps 1-6 will enable the application to make a call. The remaining steps 7-10 will enable the application to receive incoming calls in the form of push notifications using Apple’s VoIP Service.
- Install the TwilioVoice framework
- Create a Voice API key
- Configure a server to generate an access token to be used in the app
- Create a TwiML application
- Configure your application server
- Run the app
- Create a VoIP Service Certificate
- Create a Push Credential with your VoIP Service Certificate
- Configure Xcode project settings for VoIP push notifications
- Receive an incoming call
- Make client to client call
- Make client to PSTN call
<a name="bullet1"></a>1. Install the TwilioVoice framework
Carthage
Add the following line to your Cartfile
github "twilio/twilio-voice-ios"
Then run carthage bootstrap
(or carthage update
if you are updating your SDKs)
On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script in which you specify your shell (ex: /bin/sh
), add the following contents to the script area below the shell:
/usr/local/bin/carthage copy-frameworks
Add the paths to the frameworks you want to use under “Input Files”, e.g.:
$(SRCROOT)/Carthage/Build/iOS/TwilioVoice.framework
<kbd><img src="Images/carthage.png"/></kbd>
Cocoapods
Under the quickstart path, run pod install
and let the Cocoapods library create the workspace for you. Also please make sure to use Cocoapods v1.0 and later.
Once Cocoapods finishes installing, open the ObjCVoiceQuickstart.xcworkspace
and you will find a basic Objective-C quickstart project and a CallKit quickstart project.
Note: You may need to update the CocoaPods Master Spec Repo by running pod repo update master
in order to fetch the latest specs for TwilioVoice.
<a name="bullet2"></a>2. Create a Voice API key
Go to the Voice API Keys page and create a new API key:
<kbd><img src="Images/create-api-key.png"/></kbd>
Save the generated API_KEY
and API_KEY_SECRET
in your notepad. You will need them in the next step.
<a name="bullet3"></a>3. Configure a server to generate an access token to be used in the app
Download one of the starter projects for the server.
- voice-quickstart-server-java
- voice-quickstart-server-node
- voice-quickstart-server-php
- voice-quickstart-server-python
Follow the instructions in the server's README to get the application server up and running locally and accessible via the public Internet. For now just replace the Twilio Account SID that you can obtain from the console, and the API_KEY
and API_SECRET
you obtained in the previous step.
ACCOUNT_SID=AC12345678901234567890123456789012
API_KEY=SK12345678901234567890123456789012
API_KEY_SECRET=the_secret_generated_when_creating_the_api_key
<a name="bullet4"></a>4. Create a TwiML application
Next, we need to create a TwiML application. A TwiML application identifies a public URL for retrieving TwiML call control instructions. When your iOS app makes a call to the Twilio cloud, Twilio will make a webhook request to this URL, your application server will respond with generated TwiML, and Twilio will execute the instructions you’ve provided.
To create a TwiML application, go to the TwiML app page. Create a new TwiML application, and use the public URL of your application server’s /makeCall
endpoint as the Voice Request URL (If your app server is written in PHP, then you need .php
extension at the end).
<kbd><img src="Images/create-twiml-app.png"/></kbd>
As you can see we’ve used our ngrok public address in the Request URL field above.
Save your TwiML Application configuration, and grab the TwiML Application SID (a long identifier beginning with the characters AP
).
<a name="bullet5"></a>5. Configure your application server
Let's put the remaining APP_SID
configuration info into your server code
ACCOUNT_SID=AC12345678901234567890123456789012
API_KEY=SK12345678901234567890123456789012
API_KEY_SECRET=the_secret_generated_when_creating_the_api_key
APP_SID=AP12345678901234567890123456789012
Once you’ve done that, restart the server so it uses the new configuration info. Now it's time to test.
Open up a browser and visit the URL for your application server's Access Token endpoint: https://{YOUR_SERVER_URL}/accessToken
(If your app server is written in PHP, then you need .php
extension at the end). If everything is configured correctly, you should see a long string of letters and numbers, which is a Twilio Access Token. Your iOS app will use a token like this to connect to Twilio.
<a name="bullet6"></a>6. Run the app
Now let’s go back to the ObjCVoiceQuickstart.xcworkspace
. Update the placeholder of kYourServerBaseURLString
with your ngrok public URL
@import AVFoundation;
@import PushKit;
@import TwilioVoice;
static NSString *const kYourServerBaseURLString = @"https://3b57e324.ngrok.io";
static NSString *const kAccessTokenEndpoint = @"/accessToken";
static NSString *const kIdentity = @"alice";
static NSString *const kTwimlParamTo = @"to";
@interface ViewController () <PKPushRegistryDelegate, TVONotificationDelegate, TVOCallDelegate, AVAudioPlayerDelegate, UITextFieldDelegate>
@property (nonatomic, strong) NSString *deviceTokenString;
Build and run the app
<kbd><img height="667px" src="Images/build-and-run.png"/></kbd>
Leave the text field empty and press the call button to start a call. You will hear the congratulatory message. Support for dialing another client or number is described in steps 11 and 12. Tap "Hang Up" to disconnect.
<kbd><img height="667px" src="Images/hang-up.png"/></kbd>
<a name="bullet7"></a>7. Create VoIP Service Certificate
The Programmable Voice SDK uses Apple’s VoIP Services to let your application know when it is receiving an incoming call. If you want your users to receive incoming calls, you’ll need to enable VoIP Services in your application and generate a VoIP Services Certificate.
Go to Apple Developer portal and you’ll need to do the following:
- An Apple Developer membership to be able to create the certificate.
- Make sure your App ID has the “Push Notifications” service enabled.
- Create a corresponding Provisioning Profile for your app ID.
- Create an Apple VoIP Services Certificate for this app by navigating to Certificates --> Production and clicking the
+
on the top right to add the new certificate.
<kbd><img src="Images/create-voip-service-certificate.png"/></kbd>
<a name="bullet8"></a>8. Create a Push Credential with your VoIP Service Certificate
Once you have generated the VoIP Services Certificate using Keychain Access, you will need to upload it to Twilio so that Twilio can send push notifications to your app on your behalf.
Export your VoIP Service Certificate as a .p12 file from Keychain Access, then extract the certificate and private key from the .p12 file using the openssl
command. If .p12 is not an option for exporting, type voip
into the search bar of Keychain Access and make sure you select both items when exporting the certificate.
$> openssl pkcs12 -in PATH_TO_YOUR_P12 -nocerts -out key.pem
$> openssl rsa -in key.pem -out key.pem
$> openssl pkcs12 -in PATH_TO_YOUR_P12 -clcerts -nokeys -out cert.pem
Go to the Push Credentials page and create a new Push Credential. Paste the certificate and private key extracted from your certificate. You must paste the keys in as plaintext:
- For the
cert.pem
you should paste everything from-----BEGIN CERTIFICATE-----
to-----END CERTIFICATE-----
. - For the
key.pem
you should paste everything from-----BEGIN RSA PRIVATE KEY-----
to-----END RSA PRIVATE KEY-----
.
Remember to check the “Sandbox” option. This is important. The VoIP Service Certificate you generated can be used both in production and with Apple's sandbox infrastructure. Checking this box tells Twilio to send your pushes to the Apple sandbox infrastructure which is appropriate with your development provisioning profile.
Once the app is ready for store submission, update the plist with “APS Environment: production” and create another Push Credential with the same VoIP Certificate but without checking the sandbox option.
<kbd><img src="Images/add-push-credential.png"/></kbd>
Now let's go back to your server code and update the Push Credential SID. The Push Credential SID will now be embedded in your access token.
PUSH_CREDENTIAL_SID=CR12345678901234567890123456789012
<a name="bullet9"></a>9. Configure Xcode project settings for push notifications
On the project’s Capabilities tab, enable “Push Notifications”. In Xcode 8 or earlier, enable both “Voice over IP” and “Audio, AirPlay and Picture in Picture” capabilities in the Background Modes
<kbd><img src="Images/xcode-project-capabilities.png"/></kbd>
In Xcode 9+, make sure that the “Audio, AirPlay and Picture in Picture” capability is enabled and a "UIBackgroundModes" dictionary with "audio" and "voip" is in the app's plist.
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>voip</string>
</array>
<a name="bullet10"></a>10. Receive an incoming call
You are now ready to receive incoming calls. Rebuild your app and hit your application server's /placeCall endpoint: https://{YOUR_SERVER_URL}/placeCall
(If your app server is written in PHP, then you need .php
extension at the end). This will trigger a Twilio REST API request that will make an inbound call to your mobile app. Once your app accepts the call, you should hear a congratulatory message.
<kbd><img height="667px" src="Images/incoming-call.png"/></kbd>
<a name="bullet11"></a>11. Make client to client call
To make client to client calls, you need the application running on two devices. To run the application on an additional device, make sure you use a different identity in your access token when registering the new device. For example, change kIdentity
to bob
and run the application
static NSString *const kAccessTokenEndpoint = @"/accessToken";
static NSString *const kIdentity = @"bob";
static NSString *const kTwimlParamTo = @"to";
Use the text field to specify the identity of the call receiver, then tap the “Call” button to make a call. The TwiML parameters used in TwilioVoice.connect()
method should match the name used in the server.
<kbd><img height="667px" src="Images/client-to-client.png"/></kbd>
<a name="bullet12"></a>12. Make client to PSTN call
A verified phone number is one that you can use as your Caller ID when making outbound calls with Twilio. This number has not been ported into Twilio and you do not pay Twilio for this phone number.
To make client to number calls, first get a valid Twilio number to your account via https://www.twilio.com/console/phone-numbers/verified. Update your server code and replace the caller number variable (CALLER_NUMBER
or callerNumber
depending on which server you chose) with the verified number. Restart the server so that it uses the new value. Voice Request URL of your TwiML application should point to the public URL of your application server’s /makeCall
endpoint.
<kbd><img height="667px" src="Images/client-to-pstn.png"/></kbd>
Access Tokens
The access token generated by your server component is a jwt that contains a grant
for Programmable Voice, an identity
that you specify, and a time-to-live
that sets the lifetime of the generated access token. The default time-to-live
is 1 hour and is configurable up to 24 hours using the Twilio helper libraries.
Uses
In the iOS SDK the access token is used for the following:
- To make an outgoing call via
[TwilioVoice connectWithOptions:delegate:]
- To register or unregister for incoming notifications using VoIP Push Notifications via
[TwilioVoice registerWithAccessToken:deviceToken:completion:]
and[TwilioVoice unregisterWithAccessToken:deviceToken:completion:]
. Once registered, incoming notifications are handled via aTVOCallInvite
where you can choose to accept or reject the invite. When accepting the call an access token is not required. Internally theTVOCallInvite
has its own access token that ensures it can connect to our infrastructure.
Managing Expiry
As mentioned above, an access token will eventually expire. If an access token has expired, our infrastructure will return error TVOErrorAccessTokenExpired
/20104
via TVOCallDelegate
or a completion error when registering.
There are number of techniques you can use to ensure that access token expiry is managed accordingly:
- Always fetch a new access token from your access token server before making an outbound call.
- Retain the access token until getting a
TVOErrorAccessTokenExpired
/20104
error before fetching a new access token. - Retain the access token along with the timestamp of when it was requested so you can verify ahead of time whether the token has already expired based on the
time-to-live
being used by your server. - Prefetch the access token whenever the
UIApplication
, orUIViewController
associated with an outgoing call is created.
Managing Audio Interruptions
Different versions of iOS deal with AVAudioSession interruptions sightly differently. This section documents how the Programmable Voice iOS SDK manages audio interruptions and resumes call audio after the interruption ends. There are currently some cases that the SDK cannot resume call audio automatically because iOS does not provide the necessary notifications to indicate that the interruption has ended.
How Programmable Voice iOS SDK handles audio interruption
- The
TVOCall
object registers itself as an observer ofAVAudioSessionInterruptionNotification
when it's created. - When the notification is fired and the interruption type is
AVAudioSessionInterruptionTypeBegan
, theTVOCall
object automatically disables both the local and remote audio tracks. - When the SDK receives the notification with
AVAudioSessionInterruptionTypeEnded
, it re-enables the local and remote audio tracks and resumes the audio of active Calls.- We have noticed that on iOS 8 and 9, the interruption notification with
AVAudioSessionInterruptionTypeEnded
is not always fired therefore the SDK is not able to resume call audio automatically. This is a known issue and an alternative way is to use theUIApplicationDidBecomeActiveNotification
and resume audio when the app is active again after the interruption.
- We have noticed that on iOS 8 and 9, the interruption notification with
Notifications in different iOS versions
Below is a table listing the system notifications received with different steps to trigger audio interruptions and resume during an active Voice SDK call. (Assume the app is in an active Voice SDK call)
Scenario | Notification of interruption-begins | Notification of interruption-ends | Call audio resumes? | Note |
---|---|---|---|---|
PSTN Interruption | ||||
A.<br>PSTN interruption<br>Accept the PSTN incoming call<br>Remote party of PSTN call hangs up | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | |
B.<br>PSTN interruption<br>Accept the PSTN incoming call<br>Local party of PSTN call hangs up | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | |
C.<br>PSTN interruption<br>Reject PSTN | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | |
D.<br>PSTN interruption<br>Ignore PSTN | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | |
E.<br>PSTN interruption<br>Remote party of PSTN call hangs up before local party can answer | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | |
Other Types of Audio Interruption<br>(YouTube app as example) | ||||
F.<br>Switch to YouTube app and play video<br>Stop the video<br>Switch back to Voice app | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :x: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :x: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | Interruption-ended notification is not fired on iOS 9.<br>Interruption-ended notification is not fired until few seconds after switching back to the Voice app on iOS 10/11.<br>The AVAudioSessionInterruptionOptionShouldResume flag is false . |
G.<br>Switch to YouTube app and play video<br>Switch back to Voice app without stopping the video | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :x: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :x: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | Interruption-ended notification is not fired on iOS 9.<br>Interruption-ended notification is not fired until few seconds after switching back to the Voice app on iOS 10/11.<br>The AVAudioSessionInterruptionOptionShouldResume flag is false . |
H.<br>Switch to YouTube app and play video<br>Double-press Home button and terminate YouTube app<br>Back to Voice app | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | :white_check_mark: iOS 9<br>:white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | Interruption-ended notification is not fired until the Voice app is back to the active state.<br>The AVAudioSessionInterruptionOptionShouldResume flag is false . |
CallKit
On iOS 10 and later, CallKit (if integrated) takes care of the interruption by providing a set of delegate methods so that the application can respond with proper audio device handling and state transitioning in order to ensure call audio works after the interruption has ended.
Notifications & Callbacks during Interruption
By enabling the supportsHolding
flag of the CXCallUpdate
object when reporting a call to the CallKit framework, you will see the “Hold & Accept” option when there is another PSTN or CallKit-enabled call. By pressing the “Hold & Accept” option, a series of things and callbacks will happen:
- The
provider:performSetHeldCallAction:
delegate method is called withCXSetHeldCallAction.isOnHold = YES
. Put the Voice call on-hold here and fulfill the action. - The
AVAudioSessionInterruptionNotification
notification is fired to indicate the AVAudioSession interruption has started. - CallKit will deactivate the AVAudioSession of your app and fire the
provider:didDeactivateAudioSession:
callback. - When the interrupting call ends, instead of getting the
AVAudioSessionInterruptionNotification
notification, the system will notify that you can resume the call audio that was put on-hold when the interruption began by calling theprovider:performSetHeldCallAction:
method again. Note that this callback is not fired if the interrupting call is disconnected by the remote party. - The AVAudioSession of the app will be activated again and you should re-enable the audio device of the SDK
TwilioVoice.audioDevice.enabled = YES
in theprovider:didActivateAudioSession:
method.
Scenario | Audio resumes after interrupion? | Note |
---|---|---|
A.<br>Hold & Accept<br>Hang up PSTN interruption on the local end | :white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | |
B.<br>Hold & Accept<br>Remote party hangs up PSTN interruption | :x: iOS 10<br>:x: iOS 11 | provider:performSetHeldCallAction: not called after the interruption ends. |
C.<br>Hold & Accept<br>Switch back to the Voice Call on system UI | :white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | |
D.<br>Reject | :white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | No actual audio interruption happened since the interrupting call is not answered |
E.<br>Ignore | :white_check_mark: iOS 10<br>:white_check_mark: iOS 11 | No actual audio interruption happened since the interrupting call is not answered |
In case 2, CallKit does not automatically resume call audio by calling provider:performSetHeldCallAction:
method, but the system UI will show that the Voice call is still on-hold. You can resume the call using the "Hold" button, or use the CXSetHeldCallAction
to lift the on-hold state programmatically. The app is also responsible of updating UI state to indicate the "hold" state of the call to avoid user confusion.
// Resume call audio programmatically after interruption
CXSetHeldCallAction *setHeldCallAction = [[CXSetHeldCallAction alloc] initWithCallUUID:self.call.uuid onHold:holdSwitch.on];
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:setHeldCallAction];
[self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
if (error) {
NSLog(@"Failed to submit set-call-held transaction request");
} else {
NSLog(@"Set-call-held transaction successfully done");
}
}];
Managing Push Credentials
A push credential is a record for a push notification channel, for iOS this push credential is a push notification channel record for APNS VoIP. Push credentials are managed in the console under Mobile Push Credentials.
Whenever a registration is performed via TwilioVoice.registerWithAccessToken:deviceToken:completion
in the iOS SDK the identity
and the Push Credential SID
provided in the JWT based access token, along with the device token
are used as a unique address to send APNS VoIP push notifications to this application instance whenever a call is made to reach that identity
. Using TwilioVoice.unregisterWithAccessToken:deviceToken:completion
removes the association for that identity
.
Updating a Push Credential
If you need to change or update your credentials provided by Apple you can do so by selecting the Push Credential in the console and adding your new certificate
and private key
in the text box provided on the Push Credential page shown below:
<kbd><img height="667px" src="Images/update_push_credential.png"/></kbd>
Deleting a Push Credential
We do not recommend that you delete a Push Credential unless the application that it was created for is no longer being used.
If your APNS VoIP certificate is expiring soon or has expired you should not delete your Push Credential, instead you should update the Push Credential by following the Updating a Push Credential
section.
When a Push Credential is deleted any associated registrations made with this Push Credential will be deleted. Future attempts to reach an identity
that was registered using the Push Credential SID of this deleted push credential will fail.
If you are certain you want to delete a Push Credential you can click on Delete this Credential
on the console page of the selected Push Credential.
Please ensure that after deleting the Push Credential you remove or replace the Push Credential SID when generating new access tokens.
More Documentation
You can find more documentation on getting started as well as our latest AppleDoc below:
Twilio Helper Libraries
To learn more about how to use TwiML and the Programmable Voice Calls API, check out our TwiML quickstarts:
- TwiML Quickstart for Python
- TwiML Quickstart for Ruby
- TwiML Quickstart for PHP
- TwiML Quickstart for Java
- TwiML Quickstart for C#
Issues and Support
Please file any issues you find here on Github: Voice Objective-C Quickstart. Please ensure that you are not sharing any Personally Identifiable Information(PII) or sensitive account information (API keys, credentials, etc.) when reporting an issue.
For general inquiries related to the Voice SDK you can file a support ticket.
License
MIT