This tutorial explains how to develop VoIP apps using Sendbird Calls and Apple’s iOS CallKit framework. By the end of this tutorial, you will understand how to:
Each section provides the entire code of the file; you can copy and paste the code into the appropriate files. Remember that the provided code may not be the only implementation; you can customize the code to suit your needs.
Please note that you will gain the most benefit from this tutorial if you have a working knowledge of Swift.
That said, let’s get started! 💻
Now that we’ve created a Sendbird account and added users, let’s talk about how to configure CallKit for our purposes.
To develop a VoIP app service, you need a VoIP certificate for the app. Go to the Apple Developer page and sign in.
Go to Certificate, Identifiers & Profiles > Certificates > Create a New Certificate. You will find the VoIP Services Certificate under the Services section. Create the VoIP service certificate. (Here’s how to do this.)
Go to Target > Signing & Capabilities. Add Background Modes and enable Voice over IP. This will create a .entitlements file and appropriate permissions that allow you to use VoIP services. If you don’t enable Voice over IP, a CallKit error code 1 will occur.
To configure localized information for CallKit, create a file named CXProviderConfiguration.extension.swift.
CXProvider is an object that represents a telephony provider. CXProvider is initialized with CXProviderConfiguration. VoIP apps should only create one instance of CXProvider per app and use it globally. For more information, see the official Apple docs.
Each provider can specify an object conforming to the CXProviderDelegate protocol to respond to events such as starting a call, putting a call on hold, or activating a provider’s audio session.
A CXProviderConfiguration object controls the native call UI for incoming and outgoing calls, including the localized name of the provider, ringtone to be played for incoming calls, and the icon to be displayed during calls. For more information, see the official Apple docs.
|ringtoneSound||The name of the sound resource in the app bundle to be used for the provider ringtone.|
|iconTemplateImageData||The PNG data for the icon image to be displayed for the provider.|
|maximumCallGroups||The maximum number of call groups||2|
|maximumCallsPerCallGroup||The maximum number of calls per call group.||5|
|supportedHandleTypes||The supported handle types.||[ ]|
|supportsVideo||A Boolean value that indicates whether the provider supports video in addition to audio.||false|
This is a Boolean value that indicates whether the call supports video capability in addition to audio. By default, it’s set to false. See the Apple docs for more information.
If your service provides video calls, set supportsVideo to true. If your service does not provide video calls, skip this setting.
This is the type of call provider that you want to handle. See Apple’s docs about CXHandle.HandleType.
CXHandle refers to how your users are identified in each call. Three possible types of handles are: .phoneNumber, .email, and .generic. Depending on the service you provide and how you manage your users, you may choose different options. If the users are identified by their phone number or their email address, choose .phoneNumber or .email. However, if it’s based on some random UUID value or other unspecified value, use .generic, which is an unspecified String value that can be used more flexibly.
This is the PNG data for the icon image to be displayed for the provider.
According to the docs, “The icon image should be a square with a side length of 40 points. The alpha channel of the image is used to create a white image mask, which is used in the system’s native in-call UI for the button which takes the user from this system UI to the 3rd-party app.”
Set .iconTemplateImageData to the icon image that will be displayed next to the localized name on the CallKit screen. Assign .pngData() to your app icon.
We have configured CallKit and designed the UI. Now let’s understand more about CallKit actions and how to implement them.
CallKit provides many call-related features such as dialing, ending, muting, holding, etc. Each of these features should be executed by appropriate CallKit actions called CXCallAction. These actions are called from a CXCallController object, which uses CXTransaction objects to execute each CXCallAction. In order to control CallKit, you must create corresponding CXCallActions and execute them by requesting a transaction with CXTransaction.
There are three steps to send a request to CallKit:
|CXCallAction||Telephony actions, such as start call, end call, mute call, hold call, associated with a call object||Developer – CXCallAction|
|CXTransaction||An object that contains zero or more action objects to be performed by a call controller.||Developer – CXTransaction|
|CXCallController||An object interacts with calls by performing actions and observing calls.||Developer – CXCallController|
Add CXCallController property and another method named requestTransaction(with:completionHandler:). The method creates CXTransaction with CXCallAction and requests the transaction via callController. You always have to call this method after creating a CXCallAction object.
The following implements a method for CXStartCallAction. This action represents the start of a call. If the action was requested successfully, a corresponding CXProviderDelegate.provider(_:perform:) event will be called.
The following implements another method for CXEndCallAction. This action represents that the call was ended. If the action was requested successfully, a corresponding CXProviderDelegate.provider(_:perform:) event will be called. CXEndCallAction only requires the UUID of the call. Create a CXEndCallAction object with the UUID.
Other CXCallActions can be implemented the same as CXStartCallAction and CXEndCallAction. Here is the list of other call actions:
|Call Action||Description||Corresponding Event|
|CXAnswerCallAction||Answers an incoming call.||CXProviderDelegate.provider(_:perform:)|
|CXSetHeldCallAction||Places a call on hold or removes a call from hold.||CXProviderDelegate.provider(_:perform:)|
|CXSetMutedCallAction||Mutes or unmutes a call.||CXProviderDelegate.provider(_:perform:)|
|CXSetGroupCallAction||Groups a call with another call or removes a call from a group.||CXProviderDelegate.provider(_:perform:)|
To easily manage CXCallController and call IDs, you may want to create a call manager which must be accessible from anywhere. The call manager will store and manage UUIDs of the ongoing calls to handle call events.
Create a new class named CallManager. Then, add a shared static instance to access it from everywhere (You may choose to use patterns other than singleton).
If you want to know more about this pattern, see the Apple docs about managing a shared resource using a singleton.
Add callIDs property with a type of [UUID] and add methods for managing callIDs: addCall(uuid:), removeCall(uuid:) and removeAllCalls().
To report new incoming calls or respond to new CallKit actions, you have to create a CXProvider object with the CXProviderConfiguration that was created in Section 3. You can also handle CallKit events of the call via CXProviderDelegate.
To tell the CallKit that the call was ended, call reportCall(with:endedAt:reason:). This informs the end point of the call that will be displayed in the call log of the iPhone app as well.
When the provider performs CXCallActions, corresponding CXProviderDelegate methods can be called. In order to properly respond to the users’ actions, you have to implement appropriate Sendbird Calls actions in the method.
Important: Don’t forget to execute action.fulfill() before the method is ended.
For more information about CXProviderDelegate methods, refer to the official Apple docs.
You can start and end calls with CallKit using its default view. Next, let’s try to use a custom UI with CallKit. For the sake of clarity, this tutorial skips creating related storyboard files and ViewController files. Instead, suppose that there is one text field for entering the remote user’s ID, one button for making an outgoing call, another button for receiving an incoming call, and the last button for ending the call.
This is what your code will then look like:
And that’s it! You know how to build a VoIP app using Sendbird Calls and the Apple CallKit framework. In this tutorial, you learned how to configure CallKit, design a UI, as well as manage and handle CallKit actions and events. You are on your way to building awesome, engaging apps with voice and video calls. Happy iOS calling app building – we can’t wait to see what you build! 😎
Stay up-to-date on the latest technical content from Sendbird.