CallKit is a new framework introduced by Apple on iOS10 in 2016 to improve the VoIP experience.
This article will explain how to use CallKit and ZEGOCLOUD to build a Video Call App with a native calling experience.
ZEGOCLOUD is a global cloud communication service provider that was founded in June 2015. It provides high-quality audio/video communication and IM services for more than 4,000 clients around the world.
CallKit is a framework that aims to improve the VoIP experience by allowing apps to integrate with the native phone UI. By adopting CallKit, your app will be able to:
Your app will use CXProvider
to report any out-of-band notifications to the system. These are usually external events, such as an incoming call.
When such an event occurs, CXProvider
creates a CXCallUpdate
to notify the system.
CXCallUpdate
is used to encapsulate new or changing call-related information. The properties included in this class are:
CXAction
is an abstract class that represents the phone's action. For different action
, CallKit will provide different CXAction
implementations.
For example, outgoing calls are represented by CXStartCallAction
, and CXAnswerCallAction
is used to answer incoming calls. Action
is identified by a unique UUID
.
When the system wants to notify the app that it has received an event, it will inform it in the form of CXAction
.
The app communicates with CXProvider
through CXProviderDelegate
, this protocol defines the life cycle event methods of CXProvider
, and calls CXAction
.
Apps use CXCallController
to let the system know about user-initiated requests, such as "call" actions. The biggest difference between CXProvider
and CXCallController
is that the job of CXProvider
is to notify the system, while CXCallController
makes requests to the user on behalf of the system.
CXCallController
uses transactions when making requests. Transactions are represented by CXTransaction
, which contains one or more CXAction
instances. CXCallCotroller
sends the transaction to the system, and if everything is OK, the system will respond with the corresponding CXAction
to the CXProvider
.
The following will explain how to use CallKit to implement the Incoming Call function.
CXCallUpdate
and send it to the system through CXProvider
.incoming call
to its service.CXAnswerCallAction
to the CXProvider
.CXProviderDelegate
protocol method.Create a ProviderDelegate.swift
file in your project and add the following code:
import AVFoundation
import CallKit
class ProviderDelegate: NSObject {
// 1.
fileprivate let callController = CXCallController()
fileprivate let provider: CXProvider
init(callManager: CallManager) {
// 2.
provider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
// 3.
provider.setDelegate(self, queue: nil)
}
// 4.
static var providerConfiguration: CXProviderConfiguration {
let providerConfiguration = CXProviderConfiguration(localizedName: "ZEGOCall")
providerConfiguration.supportsVideo = true
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.generic]
if let iconMaskImage = UIImage(named: "IconMask") {
providerConfiguration.iconTemplateImageData = iconMaskImage.pngData()
}
roviderConfiguration.ringtoneSound = "Ringtone.caf"
return providerConfiguration
}
}
ProviderDelegate
needs to deal with CXProvider
and CXCallController
, so keep two references to both.CXProvider
with a CXProviderConfiguration
, which will be defined as a static property later. CXProviderConfiguration
is used to define the behavior and capabilities of the call.CXProvider
, you need to set its delegate.CXProviderConfiguration
supports video call, and phone number handling, and limits the number of call groups to 1. For more customization, please refer to the CallKit documentation.func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
// 1.
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
// 2.
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
print("calling")
}
}
}
This utility method allows the App to report an incoming call through the CXProvider
API. The code is explained as follows:
call update
event to the system, which contains all call-related metadata.reportIcomingCall(with:update:completion:)
method of CXProvider
to notify the system of an incoming call.The next step is to ensure protocol conformance. Still in ProviderDelegate.swift
, declare a new extension to conform to CXProviderDelegate
:
extension ProviderDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("Provider did reset")
}
CXProviderDelegate
specifies only one required method, providerDidReset(_:)
. The provider invokes this method when reset, giving your app the opportunity to clean up any ongoing calls and revert to a clean state. In this implementation, you’ll terminate the ongoing audio session and dispose of any active calls.
Open AppDelegate.swift and start by adding a new property to the class:
lazy var providerDelegate: ProviderDelegate = ProviderDelegate(callManager: self.callManager)
The provider delegate is ready for use! Add the following method to AppDelegate.swift
:
func onReceiveCallInvited(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
providerDelegate.reportIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo, completion: completion)
}
This method lets other classes access the provider delegate’s helper method.
The final piece of the puzzle is hooking up this call to the user interface. Open CallsViewController.swift, which is the controller for the main screen of the app. Find the empty implementation of unwindForNewCall(_:) and replace it with the following code:
@IBAction private func unwindForNewCall(_ segue: UIStoryboardSegue) {
// 1.
let newCallController = segue.source as! NewCallViewController
guard let handle = newCallController.handle else { return }
let videoEnabled = newCallController.videoEnabled
// 2.
let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) {
AppDelegate.shared.displayIncomingCall(uuid: UUID(), handle: handle, hasVideo: videoEnabled) { _ in
UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
}
}
}
Next, I will explain how to end a call. This App will support two ways to end a call:
Notice the difference between steps A
and B
. When the user ends the call from the in-call screen (A), the system automatically sends a CXEndCallAction
to the provider.
However, if you want to end a call using Hotline (B), it’s your job to wrap the action into a transaction and request it from the system. Once the system processes the request, it will send the CXEndCallAction
back to the provider.
However it supports ending calls, your app has to implement the necessary CXProviderDelegate
method for it to work. Open ProviderDelegate.swift
and add the following implementation to the class extension:
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
action.fulfill(withDateEnded: Date())
}
Now add the following methods to the class:
func end(call: Call) {
// 1.
let endCallAction = CXEndCallAction(call: call.uuid)
// 2.
let transaction = CXTransaction(action: endCallAction)
requestTransaction(transaction)
}
// 3.
private func requestTransaction(_ transaction: CXTransaction) {
callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction: \(error)")
} else {
print("Requested transaction successfully")
}
}
}
CXEndCallAction
. Pass in the call’s UUID
to the initializer so it can be identified later.request(_:completion:)
from the call controller. The system will request that the provider perform this transaction, which will in turn invoke the delegate method you just implemented.Like and Follow is the biggest encouragement to me
Follow me to learn more technical knowledge
Thank you for reading :)
This is one of the live technical articles. Welcome to other articles:
Also published here.