Swift show callkit to top when app active

CallKit lets users make and receive calls with VoIP (Voice over Interner Protocol) apps using the iPhone’s Phone app interface on iOS.

Swift show callkit to top when app active

What is CallKit?

CallKit helps developers to integrate and adopt native iOS video and voice calling into their VoIP apps. If you want to add CalKit support for an iOS project, you should import the framework and implement its classes and objects to handle incoming, outgoing, and active calls.

Overview

Swift show callkit to top when app active

CallKit provides you with out-of-the-box UI and removes the pain of building a custom UI from scratch. It works with both UIKit and SwiftUI. Using CallKit makes incoming and outgoing calls display the first-class citizen phone UI for iOS to users. For example, third-party iOS VOIP applications with the integration of CallKit adopt the default iOS UI elements such as accept, cancel, and mute buttons for calling. It assists in providing consistent and familiar audio and video calling experiences.

Additionally, the CallKit API displays incoming calls on the iOS device’s lock screen even when the app is closed. It manages and handles information about calls and contacts using the All and Missed Recents categories of the device’s Phone app.

CallKit Features

  • User notifications on the device's lock when it is locked.
  • Incoming calls: During an incoming call, CallKit offers a native full-screen UI for devices that do not support Dynamic Island.
  • On iPhone 14 Pro and Max, it displays incoming calls in Dynamic Island. You can interact with the Dynamic Island view by tapping it to show the full-screen UI.
  • Active call: In active call mode, the system provides rich and native iOS controls for initiating call actions such as canceling, muting, and accepting/answering calls.
  • Custom ringtone support.
  • Start and manage calls from the Recents category of the Phone app. The available call management options include Do Not Disturb, Block this Caller, and Share Contact
  • It elevates third-party VOIP apps such as WhatsApp with a native UI experience. For example, when you integrate CallKit with your app. Incoming call notifications from the app will look and feel the same as incoming notifications from the iOS Phone app.

Clone and Explore the Sample iOS App

In this article, you will create a demo iOS app (StreamCall) that can make and receive VOIP calls. Download the finished demo app from GitHub and run it on an iOS device to see how it works. After running the app, the first screen that appears is the incoming call. If you run it on iPhone 14 Pro or Max, the incoming call will appear in Dynamic Island. Tapping it in Dynamic Island displays it in full-screen mode, as demonstrated in the video below.

Understanding the CallKit Classes and API Details

Swift show callkit to top when app active

The two primary classes CallKit uses for its operations are the

let update = CXCallUpdate()

5 and

let update = CXCallUpdate()

6.

CXProvider The

let update = CXCallUpdate()

5 class handles non-user actions and out-of-band notifications like incoming calls. In addition, it keeps records of connected, ended, and rejected calls.

CXCallController The demo app called StreamCall you will build in this tutorial will use the class CXCallController to inform the system about all the local actions the user performs. For instance, this class handles user requests such as initiating outgoing calls, answering a call, and ending a call.

Create a New UIKit Project and Set info.plist

In this section, you will create a new iOS (UIKit) demo app called StreamCall using Xcode and link to the CallKit framework to provide the calling functionality.

  1. Launch Xcode and create a new iOS application. Select App from the Application category and click next.

Swift show callkit to top when app active

  1. Enter your preferred name for the project and click next. This demo uses StreamCall as the project name.

Swift show callkit to top when app active

  1. Select a location to save the app and click Create.
  2. Since this is a calling application, you can enable it to work in the background of iOS. Before the app can run in the background, the system requires you to set the background services. If you do not configure the following background execution mode, the CallKit framework will not work.
    • Select the project folder in Xcode’s Project Navigator. This folder contains the name of your app StreamCall.
    • Click the Info category and select the app name StreamCall under TARGETS.
    • Click the plus button + and choose Requires background modes from the options.
  3. Specify the Type as String and the Value as App provides Voice over IP services.

Swift show callkit to top when app active

Receiving, Connecting, and Ending Calls

The following architectural diagram represents the basic operating principle of the flow of incoming calls.

Swift show callkit to top when app active

During an incoming call, the demo app StreamCall creates a

let update = CXCallUpdate()

8. The

let update = CXCallUpdate()

5 class will send the call update to the system for publication to the user. The user will then see the result as an incoming call interface, demonstrated in the diagram above.

Accepting an Incoming Call When the user accepts the call by tapping the Accept button, it triggers an answer action and sends it to the system. The system uses

update.remoteHandle = CXHandle(type: .generic, value: "Amos Gyamfi")
//update.remoteHandle = CXHandle(type: .emailAddress, value: "[email protected]")
//update.remoteHandle = CXHandle(type: .phoneNumber, value: "+35846599990")

0 to inform the

let update = CXCallUpdate()

5 class about the answer and connects the call.

Swift show callkit to top when app active

Ending a Connected Call When the user taps the close button to end the call, it triggers the end action and sends it to the

let update = CXCallUpdate()

6 class. The

let update = CXCallUpdate()

6 packages the end-call action into

update.remoteHandle = CXHandle(type: .generic, value: "Amos Gyamfi")
//update.remoteHandle = CXHandle(type: .emailAddress, value: "[email protected]")
//update.remoteHandle = CXHandle(type: .phoneNumber, value: "+35846599990")

4 and transports it to the system. The system delivers a

update.remoteHandle = CXHandle(type: .generic, value: "Amos Gyamfi")
//update.remoteHandle = CXHandle(type: .emailAddress, value: "[email protected]")
//update.remoteHandle = CXHandle(type: .phoneNumber, value: "+35846599990")

5 to the

let update = CXCallUpdate()

5 class to end the call successfully.

Swift show callkit to top when app active

How to Receive an Incoming Call To receive an incoming call, open the Swift file *ViewController.swift, *

update.remoteHandle = CXHandle(type: .generic, value: "Amos Gyamfi")
//update.remoteHandle = CXHandle(type: .emailAddress, value: "[email protected]")
//update.remoteHandle = CXHandle(type: .phoneNumber, value: "+35846599990")

7, and add the following code snippets.

  1. Add the class

    update.remoteHandle = CXHandle(type: .generic, value: "Amos Gyamfi") //update.remoteHandle = CXHandle(type: .emailAddress, value: "[email protected]") //update.remoteHandle = CXHandle(type: .phoneNumber, value: "+35846599990")

    8 and make it conform to the

    update.remoteHandle = CXHandle(type: .generic, value: "Amos Gyamfi") //update.remoteHandle = CXHandle(type: .emailAddress, value: "[email protected]") //update.remoteHandle = CXHandle(type: .phoneNumber, value: "+35846599990")

    9 and

    let config = CallKit.CXProviderConfiguration() config.includesCallsInRecents = true; config.supportsVideo = true;

    0 protocols. *

class ViewController: UIViewController, CXProviderDelegate {
    override func viewDidLoad() {
    }
}

  1. In the closure of the

    let config = CallKit.CXProviderConfiguration() config.includesCallsInRecents = true; config.supportsVideo = true;

    1 method, create an incoming call update object. This object stores different types of information about the caller. You can use it in setting whether the call has a video.

let update = CXCallUpdate()

  1. Specify the type of information to display about the caller during an incoming call. The different types of information available include

    let config = CallKit.CXProviderConfiguration() config.includesCallsInRecents = true; config.supportsVideo = true;

    2. For example, you could use the caller's name for the generic type. During an incoming call, the name displays to the other user. Other available information types are emails and phone numbers.

update.remoteHandle = CXHandle(type: .generic, value: "Amos Gyamfi")
//update.remoteHandle = CXHandle(type: .emailAddress, value: "[email protected]")
//update.remoteHandle = CXHandle(type: .phoneNumber, value: "+35846599990")

  1. Create and set configurations about how the calling application should behave.

let config = CallKit.CXProviderConfiguration()
config.includesCallsInRecents = true;
config.supportsVideo = true;

  1. Create a CXProvider instance and set its delegate

let provider = CXProvider(configuration: config)
provider.setDelegate(self, queue: nil)

  1. Post local notification to the user that there is an incoming call. When using CallKit, you do not need to rely on only displaying incoming calls using the local notification API because it helps to show incoming calls to users using the native full-screen incoming call UI on iOS. Add the helper method below

    let config = CallKit.CXProviderConfiguration() config.includesCallsInRecents = true; config.supportsVideo = true;

    3 to show the full-screen UI. It must contain

    let config = CallKit.CXProviderConfiguration() config.includesCallsInRecents = true; config.supportsVideo = true;

    4 that helps to identify the caller using a random identifier. You should also provide the

    let update = CXCallUpdate()

    8 comprising metadata information about the incoming call. You can also check for errors to see if everything works fine.

provider.reportNewIncomingCall(with: UUID(), update: update, completion: { error in })

  1. What happens when the user accepts the call by pressing the incoming call button? You should implement the method below and call the fulfill method if the call is successful.

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        action.fulfill()
        return
    }

  1. Finally, what happens when the user taps the reject button? Call the fail method if the call is unsuccessful. It checks the call based on the UUID. It uses the network to connect to the end call method you provide.

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        action.fail()
        return
    }

Putting All the Above Code Snippets Together

Replace the content of ViewController.swift with the code below.

//Incoming Call
import UIKit
import CallKit
class ViewController: UIViewController, CXProviderDelegate {
    override func viewDidLoad() {
        // 1: Create an incoming call update object. This object stores different types of information about the caller. You can use it in setting whether the call has a video.
        let update = CXCallUpdate()
        // Specify the type of information to display about the caller during an incoming call. The different types of information available include `.generic`. For example, you could use the caller&
# 039;s name for the generic type. During an incoming call, the name displays to the other user. Other available information types are emails and phone numbers.
        update.remoteHandle = CXHandle(type: .generic, value: "Amos Gyamfi")
        //update.remoteHandle = CXHandle(type: .emailAddress, value: "[email protected]")
        //update.remoteHandle = CXHandle(type: .phoneNumber, value: "a+35846599990")
        // 2: Create and set configurations about how the calling application should behave
        let config = CallKit.CXProviderConfiguration()
        config.iconTemplateImageData = UIImage(named: "amosbw")!.pngData()
        config.includesCallsInRecents = true;
        config.supportsVideo = true;
        update.hasVideo = true
        // Provide a custom ringtone
        config.ringtoneSound = "ES_CellRingtone23.mp3";
        // 3: Create a CXProvider instance and set its delegate
        let provider = CXProvider(configuration: config)
        provider.setDelegate(self, queue: nil)
        // 4. Post local notification to the user that there is an incoming call. When using CallKit, you do not need to rely on only displaying incoming calls using the local notification API because it helps to show incoming calls to users using the native full-screen incoming call UI on iOS. Add the helper method below `reportIncomingCall` to show the full-screen UI. It must contain `UUID()` that helps to identify the caller using a random identifier. You should also provide the `CXCallUpdate` that comprises metadata information about the incoming call. You can also check for errors to see if everything works fine.
        provider.reportNewIncomingCall(with: UUID(), update: update, completion: { error in })
    }
    func providerDidReset(_ provider: CXProvider) {
    }
    // What happens when the user accepts the call by pressing the incoming call button? You should implement the method below and call the fulfill method if the call is successful.
    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        action.fulfill()
        return
    }
    // What happens when the user taps the reject button? Call the fail method if the call is unsuccessful. It checks the call based on the UUID. It uses the network to connect to the end call method you provide.
    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        action.fail()
        return
    }
}

The sample code above displays the incoming call notification in Dynamic Island on iPhone 14 Pro and Max. Tapping the incoming call notification shows the native full-screen incoming call UI.

How to Make Outgoing Calls

When you integrate CallKit with your app, it allows users to initiate a call using:

  • A button control within the outgoing call screen
  • A URL that supports Universal Links and
  • Siri.

Swift show callkit to top when app active

When the user taps the start call button to initiate a call, the demo app (StreamCall) receives a start call intent and creates an action for the call. The

let config = CallKit.CXProviderConfiguration()
config.includesCallsInRecents = true;
config.supportsVideo = true;

6 class sends the start call action to the system. The start call action has a

let config = CallKit.CXProviderConfiguration()
config.includesCallsInRecents = true;
config.supportsVideo = true;

4 that helps identify the call uniquely and a handle that defines the recipient. You can specify the recipient using a phone number, an email, or a name. The system takes the start action to the

let update = CXCallUpdate()

5 object. When the

let update = CXCallUpdate()

5 object receives and approves the

let provider = CXProvider(configuration: config)
provider.setDelegate(self, queue: nil)

0, the app presents the native outgoing call UI to the user.

Here is how to establish a basic outgoing call functionality for the app. There are other methods you can implement for the outgoing call. For example, you can add the end call method to terminate the call when the user taps the corresponding button control.

Make An Outgoing Call An outgoing call undergoes four states: starting, started, connecting, and connected. You implement the call using the Swift file ViewController.swift with the following steps.

  1. In the body

    let config = CallKit.CXProviderConfiguration() config.includesCallsInRecents = true; config.supportsVideo = true;

    1, create a provider configuration.

// 1. Add a provider configuration
        let config = CallKit.CXProviderConfiguration()
        let provider = CXProvider(configuration: config)
        provider.setDelegate(self, queue: nil)

  1. Create a start call action and configure it with UUID and the user's handle. The handle parameter is a string that represents the recipient.

let update = CXCallUpdate()

0

  1. Specify the UUID, recipient, start-call action, and transaction of the call. The UUID helps to identify the call uniquely. You can use email, phone number, or a name to specify the recipient. The transaction determines which call to put on hold for multiple calls.

let update = CXCallUpdate()

1

  1. During an outgoing call, the app must request a

    let provider = CXProvider(configuration: config) provider.setDelegate(self, queue: nil)

    0 from the

    let update = CXCallUpdate()

    6 as a transaction. The transaction object helps to hold a call when multiple calls are attempting to occur at the same time. In addition, you should provide action errors to send appropriate messages when something goes wrong. For example, you could check for bad connectivity during an outgoing call.

let update = CXCallUpdate()

2

Putting All the Above Codes Together For the Outgoing Call

let update = CXCallUpdate()

3

The code above creates a native UI for the outgoing call.

Swift show callkit to top when app active

How To Show an Outgoing Call Is Connected After a Certain Time Interval During an incoming call, the call gets connected after the user taps the accept button. When sending an outgoing call, you can use the `DispatchQueue.main.asyncAfter' method to schedule and connect it after a specified period. After this specified time, the call duration timer will start counting. You can use the following code to implement this method.

let update = CXCallUpdate()

4

The video below demonstrates a connected outgoing call with a count-up timer.

Basic CallKit Customization Options

When you provide CallKit support for an app, it allows customization through its provider configuration object. The configuration object helps you to specify whether the app supports video. During an incoming call, CallKit uses the ringtone the user sets in the setting of the iOS device. You can use the configuration object to replace the ringtone for incoming calls.

How to Change the System Ringtone The configuration provider object in CallKit provides easy ways to customize the audio or video calling to create exceptional experiences. You can swap the call icon, change the call mode, use a custom ringtone, and set whether you prefer call history to store in the Phone app’s Recents category. CallKit uses the default ringtone users set for incoming calls on their iOS devices. To change the ringtone of the CallKit app:

  1. Drag your custom sound to the Project Navigator in Xcode.
  2. Check the options Copy items if needed, Create folder references, and Add to targets. The action you just performed copies the sound to the project bundle.

Swift show callkit to top when app active

  1. You override the default ringtone by setting the ringtone property using the configuration provider

    let provider = CXProvider(configuration: config) provider.setDelegate(self, queue: nil)

    4. That creates an instance of the configuration provider that sets the custom ringtone for the incoming call screen. The sound effect in the following video (ES_CellRingtone23.mp3) used to set the ringtone property was downloaded from Epidemic Sound.

Using PushKit: How To Receive Notifications While the App Is Closed

PushKit is a framework that allows VoIP applications that support CallKit to receive notifications about incoming calls while the app is closed or running in the background. It offers developers the best notification abilities than the regular push notifications systems on iOS. It allows users to change user names and customize avatars on the app. Additionally, it helps users answer incoming calls while the app is in the background or closed.

To use PushKit for an iOS app, you should import the PushKit framework and implement the

let provider = CXProvider(configuration: config)
provider.setDelegate(self, queue: nil)

5 protocol in your app’s delegate file to enable the app to handle incoming push notifications. The second part of this tutorial will cover PushKit implementation.