iOS 10 Series: Creating VoIP Apps with CallKit

Continued from page 1. 

The controller provides valuable insight into the current system telephony state, allowing your app to determine whether an existing call is active, initiating a call, or holding a call (by notifying the telephone).

CXProvider and CXCallController (source: Apple)

CXProvider and CXCallController (source: Apple)

To help further illustrate these concepts, you'll to put the theory into action by creating an incoming call.

Creating an Incoming Call

When the app receives a call, the CXProvider would handle the call, creating a CXCallUpdate within Speakerbox, to hand it off to the system for native call handling and present it on the phone UI. Answering the call from the UI would then hand off to the system to notify our app via the same CXProvider. To end the call, the user would then end the call within the UI, which would then notify CXCallController, in a CXTransaction(CXEndCallTransaction) to let the system know the user's intent to end the call.

To handle incoming calls, start off by first creating a CXProvider and set its delegate along with a provider configuration, which sets CallKit's capabilities and more.

Answering a call (source: Apple)

Answering a call (source: Apple)

ProviderDelegate Class

This class manages the VoIP lifecycle, from outgoing and incoming call connection notifications to providing the call metadata. Speakerbox has a SpeakerManager class, which handles all the user's actions, and is referenced in this delegate class so the provider can access the list of calls via the UUID.

...
import Foundation
import UIKit
import CallKit
...
final class ProviderDelegate: NSObject, CXProviderDelegate {
    //our own callManager which we will use for the actual in- call actions
    let callManager: SpeakerboxCallManager
    private let provider: CXProvider

    init(callManager: SpeakerboxCallManager) {
        self.callManager = callManager
        provider = CXProvider(configuration: type(of: self).providerConfiguration)

        super.init()
        //set delegate to self
        provider.setDelegate(self, queue: nil)
    }

Within the class, we then set the CallKit capabilities and configurations within a static variable object, setting all the necessary properties, as follows:

...
/// The app's provider configuration, representing its CallKit capabilities
static var providerConfiguration: CXProviderConfiguration {
    let localizedName = NSLocalizedString("APPLICATION_NAME", comment: "Name of application")
    let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)

    providerConfiguration.supportsVideo = true

    providerConfiguration.maximumCallsPerCallGroup = 1

    providerConfiguration.supportedHandleTypes = [.phoneNumber]

    if let iconMaskImage = UIImage(named: "IconMask") {
        providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation(iconMaskImage)
    }

    providerConfiguration.ringtoneSound = "Ringtone.caf"

    return providerConfiguration
}

We then set the CXProvider's reportIncomingCall: delegate method, to report incoming calls to the system. Rather than relying on metadata from push notification payloads, we can now hook into the system's native incoming call UI, calling the provider to report incomingCall through the CXCallUpdate method.

/// Use CXProvider to report the incoming call to the system
    func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
        // Construct a CXCallUpdate describing the incoming call, including the caller.
        let update = CXCallUpdate()
        update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
        update.hasVideo = hasVideo

        // Report the incoming call to the system
        provider.reportNewIncomingCall(with: uuid, update: update) { error in
            /*
                Only add incoming call to the app's list of calls if the call was allowed (i.e. there was no error)
                since calls may be "denied" for various legitimate reasons. See CXErrorCodeIncomingCallError.
             */
            if error == nil {
                let call = SpeakerboxCall(uuid: uuid)
                call.handle = handle

                self.callManager.addCall(call)
            }

            completion?(error as? NSError)
        }
    }

You'll next set your provider method, getting the motions in place for answering a call and holding a call (in the subsequent methods):

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        /*
            Configure the audio session, but do not start call audio here, since it must be done once
            the audio session has been activated by the system after having its priority elevated.
         */
        configureAudioSession()

        // Trigger the call to be answered via the underlying network service.
        call.answerSpeakerboxCall()

        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        // Stop call audio whenever ending the call.
        stopAudio()

        // Trigger the call to be ended via the underlying network service.
        call.endSpeakerboxCall()

        // Signal to the system that the action has been successfully performed.
        action.fulfill()

        // Remove the ended call from the app's list of calls.
        callManager.removeCall(call)
    }

    func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
        // Retrieve the SpeakerboxCall instance corresponding to the action's call UUID
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        // Update the SpeakerboxCall's underlying hold state.
        call.isOnHold = action.isOnHold

        // Stop or start audio in response to holding or unholding the call.
        if call.isOnHold {
            stopAudio()
        } else {
            startAudio()
        }

        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }

AppDelegate Class

We will first turn our attention to the appDelegate class, where you'll work on capturing incoming CallKit triggers along with the ability to register and receive incoming notifications, using PushKit. You'll also want to reference the provider you've just created so you can respond to calls.

Doron Katz A keen passion for emerging technologies, practices and methodologies, Doron embraces the vision of lean development with continuous customer development. Consultant for various startups, as a Project and Product Manager, with a mobile engineering background in iOS, and over 10 years of professional web development experience.
 

Comments (8)

mike-tyson

I just love new iOS api CallKit. It's really fantstic to facebook & watsapp allow users to interact with incoming calls on the same level of intimacy as regular phone calls. Many users like me waiting this these kind of features. Thanks a lot Callikit.

halloween2017

Root explorer APK is the best file manager/ file explorer of root users. This will access all the including the exclusive data. I just love new iOS api CallKit. Its really fantstic to facebook & watsapp allow users to interact with incoming calls on the same level of intimacy as regular phone

kappala-vinodkumar

Thank you for sharing informative articles with amazing writing skills. Keep posting more interesting, useful information through your blog.

kicksvovoxo

You've shared a great message which I need, I am very happy and lucky to visit your perfect page, thanks much!

music-rao

We should be thankful for this.

Osvin-davinder

I learn new information from your article, you are doing a great job. Keep it up

Mageting

CallKit makes it almost unfairly easy to create VoIP applications. I like the direction that development took course on. Making tools for creation. One of the most powerful tools at the moment is spy software which can be found on http://phonetrackingreviews.com/iphone-spy-app/