iOS CallKit

Integrate iOS CallKit with the Voximplant mobile SDK.
View as Markdown

Voximplant integration with CallKit allows:

  • Displaying the system screen of an incoming call (also over third-party applications)
  • Displaying system screens of an active call or an unsuccessful call in case the phone is locked
  • Resolving the situation of multiple calls from various applications that also use CallKit API integration (switching between calls on hold, an incoming call when on a call)
  • Including call history
  • Raising the priority of the call session to the maximum, making the session uninterrupted by other applications that use audio
  • Taking into account the user’s blacklist settings, Do Not Disturb mode, Silent Mode, volume
  • Via a special mode when working with the connected audio devices (playing on all devices at the same time) to notify of an incoming call; the mode is inaccessible through the public API

You can try the demo right away.

Outgoing call integration with CallKit API

To make an outgoing call, you need to initiate a custom action CXStartCallAction with the generated UUID and report this action to the CXCallController instance. You also need to create a similar CXStartCallAction if you are handling call creation through the standard Phone application of the Recent Calls tab or via Siri.

1CXCallController *callController;
2...
3- (IBAction)startCallTouchButton:(UIButton *)sender {
4 ...
5 NSUUID *uuid = [[NSUUID alloc] init];
6 CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:contactUsername];
7 CXStartCallAction *startOutgoingCall = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:handle];
8 [callController requestTransactionWithAction:startOutgoingCall
9 completion:^(NSError * _Nullable error) {
10 if (error) {
11 ...
12 }
13 }];
14
15}
16...

After that, you have to transfer control to the CXProviderDelegate.provider (_: perform :) method by CXStartCallAction. Now you need to:

  1. Create a VICall instance.
  2. Set the VICall.callKitUUID property from the CXStartCallAction that we process.
  3. Start a call with VICall.start and inform CallKit about the start of the call via the reportOutgoingCall (with: startedConnectingAt :) method.
1var viclient: VIClient
2...
3func provider(_ callProvider: CXProvider, perform startCallAction: CXStartCallAction) {
4 let callSettings = VICallSettings()
5 callSettings.videoFlags = VIVideoFlags.videoFlags(receiveVideo: false, sendVideo: false)
6 if let vicall: VICall = viclient.call(action.handle.value, settings: callSettings) {
7 vicall.callKitUUID = startCallAction.callUUID
8 startCallAction.fulfill()
9 vicall.start()
10 callProvider.reportOutgoingCall(with: vicall.callKitUUID, startedConnectingAt: nil)
11 }
12
13 else {
14 // for example, if VIClient state is not logged in
15 startCallAction.fail()
16 }
17
18}

When the call is established, the VICallDelegate.call (_: didConnectWithHeaders :) method is called. In the handler of this event, it is necessary to inform CallKit about the call status change for the corresponding system UI update.

1var callProvider: CXCallProvider
2...
3func call(_ vicall: VICall, didConnectWithHeaders headers: [AnyHashable : Any]?) {
4 // for outgoing call:
5 callProvider.reportOutgoingCall(with: vicall.callKitUUID, connectedAt: nil);
6}
  1. If you need to update the call information, create a CXCallUpdate object and use the CXProvider.reportCall method.
1var callProvider: CXCallProvider
2...
3let callinfo = CXCallUpdate()
4callinfo.hasVideo = false
5callinfo.supportsHolding = true
6callinfo.supportsGrouping = false
7callinfo.supportsUngrouping = false
8callinfo.supportsDTMF = true
9callinfo.localizedCallerName = vicall.endpoints.first?.userDisplayName
10callProvider.reportCall(with: vicall.callKitUUID, updated: callinfo)
11...

Incoming call integration with CallKit API

In this article, we do not cover the integration of CallKit and VoIP push notifications for incoming calls.

When an incoming call is received, control transfers to the delegate method VIClientCallManagerDelegate.client (_: didReceiveIncomingCall: withIncomingVideo: headers :), and the sequence of actions you should perform is as follows:

  1. Extract the callKit UUID of the incoming call from the VICall instance.
  2. Retrieve the information about the caller from the VICall instance.
  3. Create a CXCallUpdate object and report on the new incoming call to the iOS call subsystem via the CallKit API.
1func client(_ viclient: VIClient, didReceiveIncomingCall vicall: VICall, withIncomingVideo video: Bool, headers: [AnyHashable: Any]?) {
2 let uuid = vicall.callKitUUID!
3 let username = vicall.endpoints.first?.user
4 let displayName = vicall.endpoints.first?.userDisplayName
5 let callinfo = CXCallUpdate()
6 callinfo.remoteHandle = CXHandle(type: .generic, value: username)
7 callinfo.localizedCallerName = userDisplayName
8 callProvider.reportNewIncomingCall(with: uuid, update: callinfo)
9 {
10 (error: Error?) in
11 if let error = error {
12 // CallKit can reject new incoming call in the following cases (CXErrorCodeIncomingCallError):
13 // - "Do Not Disturb" mode is on
14 // - the caller is in the system black list
15 // - ...
16 call.reject(with: .decline, headers:nil)
17 }
18 }
19
20}

The process of receiving a voice call looks like this:

1var vicall: VICall
2...
3func provider(_ callProvider: CXProvider, perform answerCallAction: CXAnswerCallAction) {
4 let callSettings = VICallSettings()
5 callSettings.videoFlags = VIVideoFlags.videoFlags(receiveVideo: false, sendVideo: false)
6 vicall.answer(with: callSettings)
7 action.fulfill()
8}

The rest manipulations

All the user actions performed through the iOS call subsystem are presented in the form of descendant classes of CXAction. If the action is successful, you need to confirm it via the action.fulfill API. If an error occurred during the execution, reject it via the action.fail API.

1func provider(_ callProvider: CXProvider, perform holdAction: CXSetHeldCallAction) {
2 vicall.setHold(holdAction.isOnHold)
3 {
4 error in
5 if let error = error {
6 holdAction.fail()
7 }
8 else {
9 holdAction.fulfill()
10 }
11 }
12
13}

Changing the audio session privileges with CallKit

The privileges of a call audio session escalate and de-escalate automatically when the iOS subsystem is using the CallKit API. It is necessary to forbid some other applications from interrupting the audio session. The call audio session itself crowds out other already active audio sessions (player and Siri, for example).

To ensure that the configuration of the Voximplant iOS SDK audio session does not conflict with the iOS audio privilege enhancement subsystem, it is extremely important to notify the Voximplant iOS SDK about the start and end of the audio session via a special API.

When starting the application, configure the audio session:

1VIAudioManager.shared().callKitConfigureAudioSession(nil)

Inform the Voximplant iOS SDK about changing the audio session status via the callkitstartaudio and callkitstopaudio methods of the VIAudioManager:

1func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
2 VIAudioManager.shared().callKitStartAudio()
3}
4func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
5 VIAudioManager.shared().callKitStopAudio()
6}

See also