08 March

CallKit

iOS 10 offers many new features. One of them is CallKit, the framework which allows our application to seamlessly integrate with the user interface of a phone. CallKit may be used in applications to allow users to receive incoming calls and perform outgoing calls with the phone-provided UI. VoIP call can be muted or suspended. It is also possible to make video calls.

Preparing the project

The first step is to prepare receiving VoIP notifications.

Configuring VoIP notifications

To configure the application to receive notifications, import framework PushKit to our AppDelegate file and create an object PKPushRegistry. Be sure to set the delegate of the newly created object as self, and set its type as VoIP:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        let pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
        pushRegistry.delegate = self
        pushRegistry.desiredPushTypes = [.voIP]

        return true
    }

Functions used for storing credentials on the server for the active user and processing pushs are:

func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, forType type: PKPushType) {
            //Store push credentials on server for the active user.
    }

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) {
    }

Supporting received notifications

The final step is to parse the data received from the function didReceiveIncomingPushWith, which, among other things, can wake the phone from the state of being locked.

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) {
        guard type == .voIP else { return }

        if let uuidString = payload.dictionaryPayload["UUID"] as? String,
           let handle = payload.dictionaryPayload["handle"] as? String,
           let hasVideo = payload.dictionaryPayload["hasVideo"] as? Bool,
           let uuid = UUID(uuidString: uuidString)
        {
             // Handle incoming call
        }
}
Setting up CallKit

The first step is to create a class to handle incoming calls by extending the class CXProviderDelegate.

final class ProviderDelegate: NSObject, CXProviderDelegate {
    private let provider: CXProvider
}

The next step is to configure the provider.

static var providerConfiguration: CXProviderConfiguration {
        let localizedName = NSLocalizedString("APPLICATION_NAME", comment: "name")
        let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)

        providerConfiguration.supportsVideo = true

        providerConfiguration.maximumCallsPerCallGroup = 1

        providerConfiguration.supportedHandleTypes = [.phoneNumber]

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

        providerConfiguration.ringtoneSound = "Ringtone.caf"

        return providerConfiguration
}

The last step is to provide an initializer, add the reportIncomingCall function responsible for the transfer of an incoming call to the system and to add providerDidReset function which is called when the provider is restarted.

init() {
        provider = CXProvider(configuration: type(of: self).providerConfiguration)
        super.init()
        provider.setDelegate(self, queue: nil)
}

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
            completion?(error as? NSError)
        }
}

func providerDidReset(_ provider: CXProvider) {
        print("Provider did reset")
        /*
            End any ongoing calls if the provider resets, and remove them from the app's list of calls,
            since they are no longer valid.
         */
    }


This is only an introduction, and if you would like to learn more about this, I recommend reading https://developer.apple.com/library/content/samplecode/Speakerbox/Introduction/Intro.html#//apple_ref/doc/uid/TP40017290-Intro-DontLinkElementID_2. There is a lot of information and a sample application there.