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.
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.
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
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.
Go to the Voice API Keys page and create a new API key:
Save the generated API_KEY
and API_KEY_SECRET
in your notepad. You will need them in the next step.
Download one of the starter projects for the server.
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
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).
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
).
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.
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
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.
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:
+
on the top right to add the new 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:
cert.pem
you should paste everything from -----BEGIN CERTIFICATE-----
to -----END CERTIFICATE-----
. 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.
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
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
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>
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.
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.
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.
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.
In the iOS SDK the access token is used for the following:
[TwilioVoice connectWithOptions:delegate:]
[TwilioVoice registerWithAccessToken:deviceToken:completion:]
and [TwilioVoice unregisterWithAccessToken:deviceToken:completion:]
. Once registered, incoming notifications are handled via a TVOCallInvite
where you can choose to accept or reject the invite. When accepting the call an access token is not required. Internally the TVOCallInvite
has its own access token that ensures it can connect to our infrastructure.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:
TVOErrorAccessTokenExpired
/20104
error before fetching a new access token.time-to-live
being used by your server.UIApplication
, or UIViewController
associated with an outgoing call is created.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.
TVOCall
object registers itself as an observer of AVAudioSessionInterruptionNotification
when it's created.AVAudioSessionInterruptionTypeBegan
, the TVOCall
object automatically disables both the local and remote audio tracks.AVAudioSessionInterruptionTypeEnded
, it re-enables the local and remote audio tracks and resumes the audio of active Calls.
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 the UIApplicationDidBecomeActiveNotification
and resume audio when the app is active again after the interruption.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. PSTN interruption Accept the PSTN incoming call Remote party of PSTN call hangs up |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
|
B. PSTN interruption Accept the PSTN incoming call Local party of PSTN call hangs up |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
|
C. PSTN interruption Reject PSTN |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
|
D. PSTN interruption Ignore PSTN |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
|
E. PSTN interruption Remote party of PSTN call hangs up before local party can answer |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
|
Other Types of Audio Interruption (YouTube app as example) |
||||
F. Switch to YouTube app and play video Stop the video Switch back to Voice app |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:x: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:x: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
Interruption-ended notification is not fired on iOS 9. Interruption-ended notification is not fired until few seconds after switching back to the Voice app on iOS 10/11. The AVAudioSessionInterruptionOptionShouldResume flag is false . |
G. Switch to YouTube app and play video Switch back to Voice app without stopping the video |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:x: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:x: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
Interruption-ended notification is not fired on iOS 9. Interruption-ended notification is not fired until few seconds after switching back to the Voice app on iOS 10/11. The AVAudioSessionInterruptionOptionShouldResume flag is false . |
H. Switch to YouTube app and play video Double-press Home button and terminate YouTube app Back to Voice app |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
:white_check_mark: iOS 9 :white_check_mark: iOS 10 :white_check_mark: iOS 11 |
Interruption-ended notification is not fired until the Voice app is back to the active state. The AVAudioSessionInterruptionOptionShouldResume flag is false . |
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.
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:
provider:performSetHeldCallAction:
delegate method is called with CXSetHeldCallAction.isOnHold = YES
. Put the Voice call on-hold here and fulfill the action.AVAudioSessionInterruptionNotification
notification is fired to indicate the AVAudioSession interruption has started.provider:didDeactivateAudioSession:
callback.AVAudioSessionInterruptionNotification
notification, the system will notify that you can resume the call audio that was put on-hold when the interruption began by calling the provider:performSetHeldCallAction:
method again. Note that this callback is not fired if the interrupting call is disconnected by the remote party.TwilioVoice.audioDevice.enabled = YES
in the provider:didActivateAudioSession:
method.Scenario | Audio resumes after interrupion? | Note |
---|---|---|
A. Hold & Accept Hang up PSTN interruption on the local end |
:white_check_mark: iOS 10 :white_check_mark: iOS 11 |
|
B. Hold & Accept Remote party hangs up PSTN interruption |
:x: iOS 10 :x: iOS 11 |
provider:performSetHeldCallAction: not called after the interruption ends. |
C. Hold & Accept Switch back to the Voice Call on system UI |
:white_check_mark: iOS 10 :white_check_mark: iOS 11 |
|
D. Reject |
:white_check_mark: iOS 10 :white_check_mark: iOS 11 |
No actual audio interruption happened since the interrupting call is not answered |
E. Ignore |
:white_check_mark: iOS 10 :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");
}
}];
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
.
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:
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.
You can find more documentation on getting started as well as our latest AppleDoc below:
To learn more about how to use TwiML and the Programmable Voice Calls API, check out our TwiML quickstarts:
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.
MIT