Making and receiving VoIP calls
Initiate outgoing calls with VoIP and configure your app to receive incoming calls.
Overview
You can handle outgoing calls in your app by providing information about the recipient and initiating the call. To receive incoming calls, you can configure your app to respond to an external VoIP notification.
Make outgoing calls
Initiate an outgoing call with a VoIP app in any of the following ways:
Perform an interaction within the app.
Open a link with a supported custom URL scheme.
Begin a VoIP call using Siri.
For more information about registering and handling URLs, see Defining a custom URL scheme for your app. For more information about initiating a call using Siri, see the INStartCallIntentHandling protocol.
To make an outgoing call, request a CXStartCallAction object. The action consists of a UUID to uniquely identify the call and a CXHandle object to specify the recipient, as shown in the following example from the VoIP calling with CallKit sample code project:
func startCall(handle: String, video: Bool = false) {
let handle = CXHandle(type: .phoneNumber, value: handle)
let startCallAction = CXStartCallAction(call: UUID(), handle: handle)
startCallAction.isVideo = video
let transaction = CXTransaction()
transaction.addAction(startCallAction)
requestTransaction(transaction)
}
private func requestTransaction(_ transaction: CXTransaction) {
callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction:", error.localizedDescription)
} else {
print("Requested transaction successfully")
}
}
}The CXHandle object specifies metadata for the outgoing call. In the example above, it uses an email address. The system on the receiving device uses this metadata to display caller information in the CallKit interface by looking for a matching contact in the recipient’s Contacts app. If the system finds a match, the CallKit interface uses the matching contact data to display the name and a photo as a poster or contact icon. If the recipient’s system can’t find a matching contact in the Contacts app and you provide a Call Directory app extension, it uses the extension to identify the caller and display rich contact information in the CallKit interface. For more information about creating a Call Directory app extension, see Identifying and blocking calls.
After you request to start a VoIP call using a CXTransaction, you need to wait for confirmation from the system to actually start the call. After you requested a call as shown in the example above, the system calls your provider delegate’s provider(_:perform:) method to let you know that it accepted the CXTransaction you created and that you can start your VoIP session. In your implementation of the provider(_:perform:) callback method, configure an AVAudioSession and call the fulfill() method on the action object when it finishes, as shown in the following code snippet from the VoIP calling with CallKit sample code project:
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
// Create and configure an instance of SpeakerboxCall to represent the new outgoing call.
let call = SpeakerboxCall(uuid: action.callUUID, isOutgoing: true)
call.handle = action.handle.value
/*
Configure the audio session but do not start call audio here.
Call audio should not be started until the audio session is activated by the system,
after having its priority elevated.
*/
configureAudioSession()
/*
Set callbacks for significant events in the call's lifecycle,
so that the CXProvider can be updated to reflect the updated state.
*/
call.hasStartedConnectingDidChange = { [weak self] in
self?.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate)
}
call.hasConnectedDidChange = {[weak self] in
self?.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectDate)
}
// Trigger the call to be started via the underlying network service.
call.startSpeakerboxCall { success in
if success {
// Signal to the system that the action was successfully performed.
action.fulfill()
// Add the new outgoing call to the app's list of calls.
self.callManager.addCall(call)
} else {
// Signal to the system that the action was unable to be performed.
action.fail()
}
}
}Receive incoming calls
To configure your app to receive incoming calls, first create a CXProvider object and store it for global access. An app reports an incoming call to the provider in response to an external notification, such as a VoIP push notification from PushKit, with the pushRegistry(_:didReceiveIncomingPushWith:for:completion:) and pushRegistry(_:didReceiveIncomingPushWith:for:completion:) callbacks.
Using the information from the external notification in the callback, the app creates a UUID and a CXCallUpdate object to uniquely identify the call and the caller. Then it passes them both to the provider using the reportNewIncomingCall(with:update:completion:) method to report the incoming call.
if let uuidString = payload.dictionaryPayload["UUID"] as? String,
let identifier = payload.dictionaryPayload["identifier"] as? String,
let uuid = UUID(uuidString: uuidString)
{
let update = CXCallUpdate()
update.callerIdentifier = identifier
provider.reportNewIncomingCall(with: uuid, update: update) { error in
// Add your implementation to report the call.
// ...
}
}After the call connects, the system calls the provider(_:perform:) method of the provider delegate. In your implementation, the delegate is responsible for configuring an AVAudioSession to initiate the audio for a call and calling fulfill() on the action when it finishes.
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// Configure the audio session.
// ...
action.fulfill()
}