Introduction CallKit is a new framework introduced by Apple on iOS10 in 2016 to improve the VoIP experience. This article will explain how to use and to build a Video Call App with a native calling experience. CallKit ZEGOCLOUD Demo download Source code download What is 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. ZEGOCLOUD What is CallKit? 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: Use the native incoming call screen in both the locked and unlocked states. Start calls from the native phone app’s Contacts, Favorites, and Recents screens. Interplay with other calls in the system. CXProvider Your app will use to report any out-of-band notifications to the system. These are usually external events, such as an incoming call. CXProvider When such an event occurs, creates a to notify the system. CXProvider CXCallUpdate CXCallUpdate is used to encapsulate new or changing call-related information. The properties included in this class are: CXCallUpdate Caller's name whether a call is video or audio-only. CXAction is an abstract class that represents the phone's action. For different , CallKit will provide different implementations. CXAction action CXAction For example, outgoing calls are represented by , and is used to answer incoming calls. is identified by a unique . CXStartCallAction CXAnswerCallAction Action 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 through , this protocol defines the life cycle event methods of , and calls . CXProvider CXProviderDelegate CXProvider CXAction CXCallController Apps use to let the system know about user-initiated requests, such as "call" actions. The biggest difference between and is that the job of is to notify the system, while makes requests to the user on behalf of the system. CXCallController CXProvider CXCallController CXProvider CXCallController uses transactions when making requests. Transactions are represented by , which contains one or more instances. sends the transaction to the system, and if everything is OK, the system will respond with the corresponding to the . CXCallController CXTransaction CXAction CXCallCotroller CXAction CXProvider Incoming Calls The following will explain how to use CallKit to implement the Incoming Call function. When an incoming call comes in, the App will create a and send it to the system through . CXCallUpdate CXProvider The system will issue an to its service. incoming call When the user answers the call, the system will send a to the . CXAnswerCallAction CXProvider App can respond to this animation by implementing the corresponding protocol method. CXProviderDelegate ProviderDelegate Create a file in your project and add the following code: ProviderDelegate.swift 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 } } needs to deal with and , so keep two references to both. ProviderDelegate CXProvider CXCallController Initialize with a , which will be defined as a static property later. is used to define the behavior and capabilities of the call. CXProvider CXProviderConfiguration CXProviderConfiguration To be able to respond to events from , you need to set its delegate. CXProvider In this app, supports video call, and phone number handling, and limits the number of call groups to 1. For more customization, please refer to the . CXProviderConfiguration CallKit documentation Just below the configuration, add the following helper method: 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 API. The code is explained as follows: CXProvider Prepare to report a event to the system, which contains all call-related metadata. call update Call the method of to notify the system of an incoming call. reportIcomingCall(with:update:completion:) CXProvider CXProviderDelegate The next step is to ensure protocol conformance. Still in , declare a new extension to conform to : ProviderDelegate.swift CXProviderDelegate extension ProviderDelegate: CXProviderDelegate { func providerDidReset(_ provider: CXProvider) { print("Provider did reset") } specifies only one required method, . 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. CXProviderDelegate providerDidReset(_:) 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) } } } Extracts the properties of the call from NewCallViewController, which is the source of this unwind segue. The user can suspend the app before the action completes, so it should use a background task. Ending the Call Next, I will explain how to end a call. This App will support two ways to end a call: The native interface ends the call. End the call in the app. Notice the difference between steps and . When the user ends the call from the in-call screen (A), the system automatically sends a to the provider. A B CXEndCallAction 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 back to the provider. CXEndCallAction CXProviderDelegate However it supports ending calls, your app has to implement the necessary method for it to work. Open and add the following implementation to the class extension: CXProviderDelegate ProviderDelegate.swift 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") } } } Create a . Pass in the call’s to the initializer so it can be identified later. CXEndCallAction UUID Wrap the action into a transaction so you can send it to the system. Invoke 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. request(_:completion:) Did You Know? 👏 and is the biggest encouragement to me Like Follow to learn more technical knowledge Follow me Thank you for reading :) Learn More This is one of the live technical articles. Welcome to other articles: All Live Streaming Features in One Article Build a Clubhouse Clone App with Android and ZEGOCLOUD Also published . here