> For a complete documentation index, fetch https://docs.voximplant.ai/llms.txt

# iOS CallKit

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](https://github.com/voximplant/ios-sdk-swift-demo/tree/master/AudioCallKit) 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](https://developer.apple.com/documentation/sirikit/instartcallintent?language=objc).

<CodeBlocks>
  ```swift title="iOS (Swift)"
  CXCallController *callController;
  ...
  - (IBAction)startCallTouchButton:(UIButton *)sender {
      ...
      NSUUID *uuid = [[NSUUID alloc] init];
      CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:contactUsername];
      CXStartCallAction *startOutgoingCall = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:handle];
      [callController requestTransactionWithAction:startOutgoingCall
       completion:^(NSError * _Nullable error) {
           if (error) {
               ...
           }
       }];

  }
  ...
  ```

  ```objc title="iOS (Objective-C)"
  CXCallController *callController;
  ...
      - (IBAction)startCallTouchButton:(UIButton *)sender {
      ...
          NSUUID *uuid = [[NSUUID alloc] init];
      CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:contactUsername];
      CXStartCallAction *startOutgoingCall = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:handle];
      [callController requestTransactionWithAction:startOutgoingCall
       completion:^(NSError * _Nullable error) {
           if (error) {
               ...
           }
       }];

  }
  ...
  ```
</CodeBlocks>

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

1. Create a [VICall](https://voximplant.com/docs/references/iossdk/call/vicall) instance.
2. Set the [VICall.callKitUUID](https://voximplant.com/docs/references/iossdk/call/vicall#callkituuid) property from the CXStartCallAction that we process.
3. Start a call with [VICall.start](https://voximplant.com/docs/references/iossdk/call/vicall#start) and inform CallKit about the start of the call via the **reportOutgoingCall (with: startedConnectingAt :)** method.

<CodeBlocks>
  ```swift title="iOS (Swift)"
  var viclient: VIClient
  ...
  func provider(_ callProvider: CXProvider, perform startCallAction: CXStartCallAction) {
      let callSettings = VICallSettings()
      callSettings.videoFlags = VIVideoFlags.videoFlags(receiveVideo: false, sendVideo: false)
      if let vicall: VICall = viclient.call(action.handle.value, settings: callSettings) {
          vicall.callKitUUID = startCallAction.callUUID
          startCallAction.fulfill()
          vicall.start()
          callProvider.reportOutgoingCall(with: vicall.callKitUUID, startedConnectingAt: nil)
      }

      else {
          // for example, if VIClient state is not logged in
          startCallAction.fail()
      }

  }
  ```

  ```objc title="iOS (Objective-C)"
  VIClient *viclient;
  ...
      - (void)provider:(CXProvider *)callProvider performStartCallAction:(CXStartCallAction *)startCallAction {
      VICallSettings *callSettings = [[VICallSettings alloc] init];
      callSettings.videoFlags = [VIVideoFlags   videoFlagsWithReceiveVideo:NO sendVideo:NO];
      VICall *vicall = [viclient call:startCallAction.handle.value settings:callSettings];
      if (vicall) {
          vicall.callKitUUID = startCallAction.callUUID;
          [startCallAction fulfill];
          [vicall start];
          [callProvider reportOutgoingCallWithUUID:startCallAction.callUUID startedConnectingAtDate:nil];
      }

      else {
          // for example, if VIClient state is not logged in
          [startCallAction fail];
      }

  }
  ```
</CodeBlocks>

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.

<CodeBlocks>
  ```swift title="iOS (Swift)"
  var callProvider: CXCallProvider
  ...
  func call(_ vicall: VICall, didConnectWithHeaders headers: [AnyHashable : Any]?) {
      // for outgoing call:
      callProvider.reportOutgoingCall(with: vicall.callKitUUID, connectedAt: nil);
  }
  ```

  ```objc title="iOS (Objective-C)"
  CXCallProvider *callProvider;
  ...
      - (void)call:(VICall *)vicall didConnectWithHeaders:(NSDictionary *)headers {
      // for outgoing call:
      [callProvider reportOutgoingCallWithUUID:vicall.callKitUUID connectedAtDate:nil];
  }
  ```
</CodeBlocks>

4. If you need to update the call information, create a **CXCallUpdate** object and use the **CXProvider.reportCall** method.

<CodeBlocks>
  ```swift title="iOS (Swift)"
  var callProvider: CXCallProvider
  ...
  let callinfo = CXCallUpdate()
  callinfo.hasVideo = false
  callinfo.supportsHolding = true
  callinfo.supportsGrouping = false
  callinfo.supportsUngrouping = false
  callinfo.supportsDTMF = true
  callinfo.localizedCallerName = vicall.endpoints.first?.userDisplayName
  callProvider.reportCall(with: vicall.callKitUUID, updated: callinfo)
  ...
  ```

  ```objc title="iOS (Objective-C)"
  CXCallProvider *callProvider;
  ...
  CXCallUpdate *callInfo = [[CXCallUpdate alloc] init];
  callInfo.hasVideo = NO;
  callInfo.supportsHolding = YES;
  callInfo.supportsGrouping = NO;
  callInfo.supportsUngrouping = NO;
  callInfo.supportsDTMF = YES;
  callInfo.localizedCallerName = vicall.endpoints.firstObject.userDisplayName;
  [callProvider reportCallWithUUID:vicall.callKitUUID updated:callInfo];
  ...
  ```
</CodeBlocks>

## 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.

<CodeBlocks>
  ```swift title="iOS (Swift)"
  func client(_ viclient: VIClient, didReceiveIncomingCall vicall: VICall, withIncomingVideo video: Bool, headers: [AnyHashable: Any]?) {
      let uuid = vicall.callKitUUID!
      let username = vicall.endpoints.first?.user
      let displayName = vicall.endpoints.first?.userDisplayName
      let callinfo = CXCallUpdate()
      callinfo.remoteHandle = CXHandle(type: .generic, value: username)
      callinfo.localizedCallerName = userDisplayName
      callProvider.reportNewIncomingCall(with: uuid, update: callinfo)
      {
          (error: Error?) in
          if let error = error {
              // CallKit can reject new incoming call in the following cases (CXErrorCodeIncomingCallError):
              // - "Do Not Disturb" mode is on
              // - the caller is in the system black list
              // - ...
              call.reject(with: .decline, headers:nil)
          }
      }

  }
  ```

  ```objc title="iOS (Objective-C)"
  - (void)client:(nonnull VIClient *)viclient didReceiveIncomingCall:(nonnull VICall *)call withIncomingVideo:(BOOL)video headers:(nullable NSDictionary *)headers {
      NSUUID *uuid = vicall.callKitUUID;
      NSString *username = vicall.endpoints.firstObject.user;
      NSString *displayName = vicall.endpoints.firstObject.userDisplayName;
      CXCallUpdate *callInfo = [[CXCallUpdate alloc] init];
      callInfo.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:username];
      callInfo.supportsHolding = YES;
      callInfo.supportsGrouping = NO;
      callInfo.supportsUngrouping = NO;
      callInfo.supportsDTMF = YES;
      callInfo.hasVideo = NO;
      callInfo.localizedCallerName = displayName;
      [callProvider reportNewIncomingCallWithUUID:uuid update:callInfo completion:^(NSError * _Nullable error) {
          if (error) {
              // CallKit can reject new incoming call in the following cases (CXErrorCodeIncomingCallError):
              // - "Do Not Disturb" mode is on
              // - the caller is in the system black list
              // - ...
              [vicall rejectWithMode:VIRejectModeDecline headers:nil];
          }
      }];
  ```
</CodeBlocks>

The process of receiving a voice call looks like this:

<CodeBlocks>
  ```swift title="iOS (Swift)"
  var vicall: VICall
  ...
  func provider(_ callProvider: CXProvider, perform answerCallAction: CXAnswerCallAction) {
      let callSettings = VICallSettings()
      callSettings.videoFlags = VIVideoFlags.videoFlags(receiveVideo: false, sendVideo: false)
      vicall.answer(with: callSettings)
      action.fulfill()
  }
  ```

  ```objc title="iOS (Objective-C)"
  VICall *vicall;
  ...
      - (void)provider:(CXProvider *)callProvider performAnswerCallAction:(CXAnswerCallAction *)answerCallAction {
      VICallSettings *callSettings = [[VICallSettings alloc] init];
      callSettings.videoFlags = [VIVideoFlags videoFlagsWithReceiveVideo:NO sendVideo:NO];
      [vicall answerWithSettings:callSettings];
      [action fulfill];
  }
  ```
</CodeBlocks>

## 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.

<CodeBlocks>
  ```swift title="iOS (Swift)"
  func provider(_ callProvider: CXProvider, perform holdAction: CXSetHeldCallAction) {
      vicall.setHold(holdAction.isOnHold)
      {
          error in
          if let error = error {
              holdAction.fail()
          }
          else {
              holdAction.fulfill()
          }
      }

  }
  ```

  ```objc title="iOS (Objective-C)"
  - (void)provider:(CXProvider *)callProvider performSetHeldCallAction:(CXSetHeldCallAction *)holdAction {
      [vicall setHold:holdAction.isOnHold completion:^(NSError * _Nullable error) {
          if (error) {
              [action fail];
          }
          else {
              [action fulfill];
          }
      }];

  }
  ```
</CodeBlocks>

## 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:

<CodeBlocks>
  ```swift title="iOS (Swift)"
  VIAudioManager.shared().callKitConfigureAudioSession(nil)
  ```

  ```objc title="iOS (Objective-C)"
  [VIAudioManager.sharedAudioManager callKitConfigureAudioSession:nil];
  ```
</CodeBlocks>

Inform the Voximplant iOS SDK about changing the audio session status via  the [callkitstartaudio](https://voximplant.com/docs/references/iossdk/hardware/viaudiomanager#callkitstartaudio) and [callkitstopaudio](https://voximplant.com/docs/references/iossdk/hardware/viaudiomanager#callkitstopaudio) methods of the [VIAudioManager](https://voximplant.com/docs/references/iossdk/hardware/viaudiomanager):

<CodeBlocks>
  ```swift title="iOS (Swift)"
  func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
      VIAudioManager.shared().callKitStartAudio()
  }
  func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
      VIAudioManager.shared().callKitStopAudio()
  }
  ```

  ```objc title="iOS (Objective-C)"
  - (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession {
      [[VIAudioManager sharedAudioManager] callKitStartAudio];
  }
  - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession {
      [[VIAudioManager sharedAudioManager] callKitStopAudio];
  }
  ```
</CodeBlocks>

## See also

* [CallKit integration to Flutter applications on iOS](https://github.com/voximplant/flutter_callkit)