CallKit Tutorial for iOS
Learn how your app can use CallKit for system-level phone integration and how to build a directory extension for call blocking and identification. By Andrew Kharchyshyn.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
CallKit Tutorial for iOS
30 mins
- Getting Started
- What is CallKit?
- CXProvider
- CXCallController
- Incoming Calls
- ProviderDelegate
- CXProviderDelegate
- Test Flight
- Ending the Call
- CXProviderDelegate
- Requesting Transactions
- Other Provider Actions
- Handling Outgoing Calls
- Starting the Call
- Managing Multiple Calls
- Creating a Call Directory Extension
- Settings Setup
- Where to Go From Here?
Life on iOS wasn’t always perfect for VoIP (Voice over IP) app developers. In particular, delivering notifications was tough. With your app in the background, a user’s only option was a regular notification, which is easy to miss. Without a rich, built-in call UI, your app wouldn’t feel very integrated.
Fortunately, Apple introduced CallKit in iOS 10!
In this tutorial you’ll get a glimpse of CallKit’s power by building an app that:
- Uses system services to report incoming and outgoing calls.
- Manages a call directory to identify or block incoming calls.
Getting Started
Download the starter project using the Download Materials button at the top or bottom of this tutorial. Then, set up code signing to debug the project on your device. Open the project file in Xcode and select Hotline in the Project navigator.
Start by changing the bundle identifier. With the project selected, go to the General tab and find the Identity section. Change the bundle identifier to something unique:
Next, look for the Signing section. Select your preferred development team (in my case, it’s my personal team) in the drop-down next to Team. Be sure to check Automatically manage signing. This allows Xcode to automatically create the provisioning profile for the app.
To test your setup, build and run the app on your iPhone.
Currently the app won’t do much, but you’ll notice that there are several source files in the starter project. Those files are mostly responsible for setting up the UI and handling user interactions. There are two main classes worth a look before moving on:
-
Call
represents a phone call. The class exposes properties for identifying calls (such as its UUID, or handle) as well as lifecycle callbacks indicating when the user starts, answers or ends a call. -
CallManager
currently maintains the list of ongoing calls in the app and has methods for adding or removing calls. You’ll expand this class further throughout the tutorial.
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.
In this section, you’ll get more familiar with the CallKit architecture. The diagram below shows all the key players:
When working with CallKit, there are two primary classes you’ll interact with: CXProvider
and CXCallController
. Time to dive in!
CXProvider
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 call update to notify the system. Call updates encapsulate new or changed call-related information. They are of the CXCallUpdate
class, which exposes properties such as the caller’s name or whether a call is video or audio-only.
When the system wants to notify the app of events, it usesCXAction
instances. CXAction
is an abstract class representing telephony actions. For each action, CallKit provides a different concrete implementation of CXAction
. For instance, CXStartCallAction
represents initiating an outgoing call, while CXAnswerCallAction
answers an incoming call. A unique UUID identifies each action, which can fail or fulfill.
Apps can communicate with CXProvider
through the CXProviderDelegate
protocol, which defines methods for provider lifecycle events and incoming actions.
CXCallController
The app will use CXCallController
to inform the system of user-initiated requests, such as a Start call action. This is the key difference between the CXProvider
and the CXCallController
: The provider reports to the system whereas the call controller makes requests from the system on behalf of the user.
The call controller uses transactions to make these requests. Transactions, represented by CXTransaction
, contain one or more CXAction
instances. The call controller sends transactions to the system. If everything is in order, the system responds with the appropriate action to the provider.
What does this look like in practice?
Incoming Calls
The diagram below shows a high-level overview of an incoming call flow:
- In response to an incoming call, the app constructs a
CXCallUpdate
and uses the provider to send it to the system. - The system publishes this as an incoming call to all of its services.
- When the user answers the call, the system sends a
CXAnswerCallAction
instance to the provider. - The app answers the call by implementing the appropriate
CXProviderDelegate
method.
ProviderDelegate
First, create the delegate for the provider. Head back to Xcode, and with the App group highlighted in the Project navigator, create a new file called ProviderDelegate.swift.
Add the following code to the file:
import AVFoundation
import CallKit
class ProviderDelegate: NSObject {
// 1.
private let callManager: CallManager
private let provider: CXProvider
init(callManager: CallManager) {
self.callManager = callManager
// 2.
provider = CXProvider(configuration: ProviderDelegate.providerConfiguration)
super.init()
// 3.
provider.setDelegate(self, queue: nil)
}
// 4.
static var providerConfiguration: CXProviderConfiguration = {
let providerConfiguration = CXProviderConfiguration(localizedName: "Hotline")
providerConfiguration.supportsVideo = true
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber]
return providerConfiguration
}()
}
This is what’s happening in the code above:
- Store references to both the provider and the call controller. The provider delegate will interact with both of them.
- Initialize the provider with the appropriate
CXProviderConfiguration
, stored as a static variable below. A provider configuration specifies the behavior and capabilities of the calls. - Set the delegate to respond to events coming from the provider. This line will cause a build error, as
ProviderDelegate
doesn’t conform toCXProviderDelegate
yet. - In the case of Hotline, the provider configuration allows video calls and phone number handles and restricts the number of call groups to one. For further customization, refer to the CallKit documentation.
Just below the configuration, add the following helper method:
func reportIncomingCall(
uuid: UUID,
handle: String,
hasVideo: Bool = false,
completion: ((Error?) -> 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 {
// 3.
let call = Call(uuid: uuid, handle: handle)
self.callManager.add(call: call)
}
// 4.
completion?(error)
}
}
This helper method allows the app to call the CXProvider
API to report an incoming call. Here’s what’s going on:
- Prepare a call update for the system which will contain the relevant call metadata.
- Invoke
reportNewIncomingCall(with:update:completion)
on the provider to notify the system of the incoming call. - The completion handler will be called once the system processes the call. Assuming no errors, you create a
Call
instance and add it to the list of calls via theCallManager
. - Invoke the completion handler if it’s not
nil
.
Other classes in the app can invoke this method in order to simulate incoming calls.