Skip to main content

How to build a VoIP app with iOS CallKit and Sendbird Calls

Jaesung Lee
Engineer
  • Tutorial Type: Getting Started
  • Reading Time: 30 mins
  • Building Time: 2-4 hrs
Chat SDK v4 2x

Swift, Kotlin, and TypeScript SDKs

Build in-app chat, calls, and live streaming

Introduction

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:

  • Configure CallKit
  • Design the CallKit UI
  • Execute CallKit actions
  • Create a call manager
  • Handle CallKit events
  • Enable interaction with a custom UI

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! 💻

Step 1. Create a Sendbird account

  1. Sign up for a free Sendbird account.
  2. Create an account using your email or select ‘Continue with Google’.
  3. Set up your organization by filling out the fields ‘Organization name’ and ‘Phone number’.
  4. Create a new “Chat + Calls” application in the region closest to your locale.
    • On the left side of the screen, you should see a ‘Calls’ menu. Go into the ‘Studio’ and create a new user. Follow the prompts on your screen.
Tutorial Create Sendbird account

Step 2: Configuring CallKit

Now that we’ve created a Sendbird account and added users, let’s talk about how to configure CallKit for our purposes.

2.1

To develop a VoIP app service, you need a VoIP certificate for the app. Go to the Apple Developer page and sign in.

2.2

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.)

2.3

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.

Tutorial background modes

Step 3: Designing the CallKit UI

Tutorial Designing CallKit UI

3.1

To configure localized information for CallKit, create a file named CXProviderConfiguration.extension.swift.

CXProvider

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.

3.2

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.

  1. Initialize the CXProviderConfiguration object with a localized name. This name will appear in the call view when your users receive a call through CallKit. Use appropriate naming, such as your app service name, as the localized name. In this case, we used ‘Homing Pigeon’.
  2. Configure the user interface and its capabilities. In this step, set iconTemplateImageData, supportsVideo, and supportedHandleTypes. If you want to further customize CallKit, see the table below. You can also refer to the official Apple docs.
NameDescriptionDefault Value
ringtoneSoundThe name of the sound resource in the app bundle to be used for the provider ringtone.
iconTemplateImageDataThe PNG data for the icon image to be displayed for the provider.
maximumCallGroupsThe maximum number of call groups2
maximumCallsPerCallGroupThe maximum number of calls per call group.5
supportedHandleTypesThe supported handle types.[ ]
supportsVideoA Boolean value that indicates whether the provider supports video in addition to audio.false

supportsVideo

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.

supportedHandleTypes

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.

iconTemplateImageData

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.

Step 4. Requesting CallKit actions

We have configured CallKit and designed the UI. Now let’s understand more about CallKit actions and how to implement them.

Tutorial CXCallController

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:

  1. Create CXCallAction object
  2. Create CXTransaction object
  3. Request the CXTransaction object via CXCallController
NameDescriptionReference
CXCallActionTelephony actions, such as start call, end call, mute call, hold call, associated with a call object.Developer – CXCallAction
CXTransactionAn object that contains zero or more action objects to be performed by a call controller.Developer – CXTransaction
CXCallControllerAn object interacts with calls by performing actions and observing calls.Developer – CXCallController

4.1 Transaction

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.

4.2. Call actions

Start call action

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.

1. You have to create a CXHandle object associated with the call that will be used to identify the users involved with the call. This object will be included in CXStartCallAction along with a UUID.

2. If the call has video, set .isVideo to true.

3. As mentioned above, don’t forget to call the requestTransaction(with:completionHandler:) method after creating a CXStartCallAction object.

End call action

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 call actions

Other CXCallActions can be implemented the same as CXStartCallAction and CXEndCallAction. Here is the list of other call actions:

Call ActionDescriptionCorresponding Event
CXAnswerCallActionAnswers an incoming call.CXProviderDelegate.provider(_:perform:)
CXSetHeldCallActionPlaces a call on hold or removes a call from hold.CXProviderDelegate.provider(_:perform:)
CXSetMutedCallActionMutes or unmutes a call.CXProviderDelegate.provider(_:perform:)
CXSetGroupCallActionMutes or unmutes a call.CXProviderDelegate.provider(_:perform:)

Step 5. Managing calls

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.

NOTE: You can also use the CXCallController.callObserver.calls property that manages a list of active calls (including ended calls) and observes call changes. Each call is a CXCall object that represents a call in CallKit. By checking the hasEnded attribute, you can handle ongoing calls.

For more information, see the docs about CXCallObserver and the docs about CXCall.

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().

Step 6. Handling CallKit events

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.

1. Import CallKit and create a ProviderDelegate class with NSObject and CXProviderDelegate conformance.

2. Add two properties: callManager and provider. The callManager is the CallManager class that you created in Section 5. The provider reports actions for CallKit. When you initialize a provider, use the CXProviderConfiguration.custom that you already created.

3. To report a new incoming call, you need to create a CXCallUpdate instance with the relevant information about the incoming call, as well as the CXHandle that identifies the users involved in the call.

4. To make your calls richer, you can customize the CXHandle and CXCallUpdate instances. If the call has video, set hasVideo to true. The upper iPhone call log is based on the CXHandle object.

5. After reporting a new incoming call, you have to add it to CallManager.shared.calls by using the addCall(uuid:) method that was added earlier.

6. CallKit keeps track of the connected time of the call and the end time of the call by listening to appropriate CallKit events. To tell the CallKit that the call was connected, call reportOutgoingCall(with:connectedAt:). This initiates the call duration elapsing, and informs the starting point of the call that is displayed in the call log of the iPhone app.

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.

Step 7. Handle CXCallAction event

Interaction with CallKit UI

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

Note: To access the UUID of the call, you have to use action.callUUID property, not action.uuid.

Tutorial method table

For more information about CXProviderDelegate methods, refer to the official Apple docs.

Step 8. Interaction with UI

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:

  1. Make an outgoing call: Because the user is initiating a call, you have to create a request for the call. This action requires the callee’s user ID and unique UUID of the call.
  2. Implement the action for the end button: This action will end the call based on the callID.
  3. Answer an incoming audio call: To do this, you have to simulate an incoming audio call. Because CallKit is not aware of the incoming call, you have to report to CallKit about the incoming call. This action requires the caller’s user ID and unique UUID of the call. Currently, because the incoming call is made locally, you will use a randomly generated UUID() instead of a real call’s UUID. If you want to test incoming video calls, assign the value of hasVideo parameter as true.

Conclusion

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! 😎