How to use the CallKit and EnableX APIs to build iOS in-app calling
Tags: videoapi, voip, callkit, webrtc
Published: 2024-06-10 11:47:37

Apps such as Skype, WhatsApp, and Messenger have revolutionized the way we communicate with App-to-App calling, while **[WEBRTC](https://www.enablex.io/insights/the-most-comprehensive-guide-on-webrtc/)** has taken it a step further by seamlessly integrating these capabilities into browsers and mobile apps. However, even with the advancements in technology, a lack of user-friendly interfaces and effective notification systems can hinder the user experience. While VOIP and WebRTC focus on the technical aspects of real-time communication, users primarily seek convenience in making and receiving calls. Recognizing this, Apple introduced CallKit to enhance the user experience. In this article, we'll explore how to harness the power of the WebRTC-based **[CPaaS platform](https://www.enablex.io/cpaas/voice-api)**, EnableX, to develop a native dialer interface on iOS. By leveraging CallKit and EnableX **[Video APIs](https://www.enablex.io/cpaas/video-api)**, we can replicate the seamless App-to-App calling experience for users.

**What exactly is CallKit?**

CallKit, a framework unveiled alongside iOS 10 by Apple, revolutionizes the VoIP landscape by facilitating seamless integration of third-party call-related applications with the native phone interface. This advancement significantly elevates user experience by eliminating the necessity to toggle between apps when managing calls, whether it's answering, terminating, or blocking. CallKit empowers your application to:

1. Showcase incoming and outgoing calls directly on the native call screen, whether the device is locked or unlocked.

2. Initiate calls through any third-party calling service, such as various **[VOIP](https://www.enablex.io/cpaas/voice-api)** services, seamlessly integrated with the iPhone, like EnableX.

3. Conduct in-app voice calls directly from the native phone app's Contacts, Favorites, or Recent screens.

Before the advent of CallKit, delivering call notifications posed a considerable challenge, often resulting in missed calls as they were buried within regular notifications. CallKit has been a game-changer for software developers, providing them with a native iOS UI for VoIP apps, thereby ensuring seamless integration that was previously unattainable without a robust, built-in call UI.

It's crucial to note that while CallKit offers a native UI, it relies on the Apple Push Notification Service (APN) or any third-party notification service to alert users about incoming and outgoing calls. Therefore, when configuring a certificate for notifying users about calls, remember to enable the VoIP call option.

**How do you utilize CallKit on iOS?**

Achieving this is straightforward with the following steps:

•	Acquire fundamental knowledge of Swift/Objective-C and Xcode.
•	Register for a complimentary developer account on the EnableX portal.
•	Employ CocoaPods to install necessary project files and dependencies.
•	Establish a CX Provider.
•	Initiate and respond to a call using CallKit. Then, seamlessly integrate EnableX APIs with CallKit.

**What is meant by EnableX?**

EnableX provides a versatile communications solution, empowering developers to seamlessly integrate **[video](https://www.enablex.io/cpaas/video-api)**, **[voice](https://www.enablex.io/cpaas/voice-api)**, **[SMS](https://www.enablex.io/cpaas/sms-api)**, and chat messaging functionalities into various applications and websites. Leveraging a robust carrier-grade infrastructure, it equips developers with a comprehensive suite of communication APIs and essential toolkits, facilitating the creation of dynamic and immersive communication experiences.

**Prerequisites for creating iOS in-app calling.**

Before diving into the detailed instructions, ensure you have the following prerequisites and setups in place:

1. Familiarity with Swift/Objective-C and Xcode at a basic to intermediate level.

2. Access to Xcode 10 or newer and iOS devices running on iOS 12.0 or later. Note that CallKit features will not function in the simulator.

3. **[Create a free developer's account on EnableX](https://portal.enablex.io/cpaas/trial-sign-up/?_gl=1*1d5oqpu*_gcl_au*MjA0OTkzNzYzMy4xNzE2NDQzMjg5*_ga*MTQ5NTkzNDI2LjE3MTY0NDMyODk.*_ga_XK68MTBLGX*MTcxODAxNzUzMy4xOC4xLjE3MTgwMTkwOTcuNDQuMC4w)**. This process is secure and does not require any credit card information.

4. Utilize CocoaPods for installing project files and dependencies. Consult the **[CocoaPods Getting Started Guide](https://guides.cocoapods.org/using/getting-started.html#getting-started)** for installation instructions. 4.1. Launch Terminal, navigate to your project directory, and execute the command: pod install. 4.2. Reopen your project in Xcode using the newly generated *.xcworkspace file.

Before delving into API usage, let's familiarize ourselves with the key CallKit and EnableX classes, which encompass various methods essential for conducting end-to-end video communication.

**Principal Classes for EnableX**

EnxRtc: Within this class lie functionalities for room connection and room entry. EnxRoom: This class encompasses functionalities for managing operations within a room. These include connecting endpoints to EnableX Room, managing the publication and subscription of streams, and more. EnxStream: All functionalities concerning media stream operations are encapsulated within this class. This includes initiating, configuring, and transporting streams to and from Media Servers, as well as receiving streams for playback. EnxPlayerView: This class facilitates the display of video streams on an EnxPlayerView. For further understanding of fundamentals, refer to the **[Official EnableX developer documentation.](https://www.enablex.io/developer/video-api/client-api/android toolkit/)**

**Principal CallKit Courses**

A CXProvider instance represents a telephony service provider, tasked with relaying system notifications as they occur. When building a VoIP application, it's advisable to instantiate just one CXProvider object and maintain it globally for accessibility. Upon initialization, a CXProvider object requires a CXProviderConfiguration instance to define its operational characteristics and call capabilities. Additionally, each provider can designate a CXProviderDelegate-conforming object to handle various events, such as call initiation, call holding, or activation of the provider's audio session.

Steps for creating a provider:

1.	Create an instance of CXProviderConfiguration and set the caller's name for display on the dialer screen. For example:

let provider configuration = CXProviderConfiguration(localized name: "EnxCall")
2. Specify support for call types, such as audio or video:
provider configuration.supports video = false
 
3. Define the maximum number of calls allowed per group:
provider configuration.maximumCallsPerCallGroup = 1

4. Set the supported types of call handles, including PhoneNumber, generic, or email address:

provider configuration.supportedHandleTypes = [.phoneNumber]
5. Choose a dialer ringtone sound:

providerConfiguration.ringtoneSound = "callTone.caf"
6. Initialize the provider with the configured settings:

let provider = CXProvider(configuration: provider configuration)
provider.set delegate(self as? CXProviderDelegate, queue: nil)
CXProviderDelegate:

The CXProviderDelegate protocol defines methods invoked by CXProvider to handle various events, including provider resets, transaction requests, action executions, and audio session activation state changes.

Useful methods in CXProviderDelegate:
1. Handling Provider Events:
o	func providerDidReset(_ provider: CXProvider): Invoked when the provider resets.

2. Handling Call Actions:
o	func provider(_ provider: CXProvider, act: CXAnswerCallAction): Invoked when the provider executes the answer call action.
o	func provider(_ provider: CXProvider, act: CXEndCallAction): Invoked when the provider executes the end call action.
o	func provider(_ provider: CXProvider, act: CXSetHeldCallAction): Invoked when the provider executes the set held call action.
o	func provider(_ provider: CXProvider, act: CXSetMutedCallAction): Invoked when the provider executes the set muted call action.
o	func provider(_ provider: CXProvider, timedOutPerforming action: CXAction): Invoked when the provider times out while performing a specific action.

3.	Handling Changes to Audio Session Activation State:
o	func provider(_ provider: CXProvider, deactivate audioSession: AVAudioSession): Invoked when the provider's audio session is activated.
o	func provider(_ provider: CXProvider, deactivate audioSession: AVAudioSession): Invoked when the provider's audio session is deactivated.
CXCallController:
CXCallController manages observation and interaction with calls. Unlike CXProvider, which reports to the system, CXCallController interacts with the system on behalf of the user, initiating requests like starting a call. It communicates these requests through instances of CXCallAction subclasses such as CXEndCallAction for call termination or CXSetHeldCallAction for placing calls on hold. Multiple actions can be requested within a single CXTransaction object, subject to system approval. Each CXCallController instance oversees a CXCallObserver object, accessible via the callObserver property, allowing notification of any changes to active calls.

**How to use CallKit to make a call**

1.	Initialize a CXAction to begin the call.

let callHandle = CXHandle(type: .phoneNumber, value: handle)
let startCallAction = CXStartCallAction(call: UUID(), handle: callHandle)

2.	Create a CXTransaction to encapsulate the action.
let call transaction = CXTransaction()
call transaction.addiction(startCallAction)
3.	Request the transaction.
let actionName = "start call"
call controller.request(call transaction) { error in
    if let error = error {
        print("Error requesting transaction: \(error)")
    } else {
        print("Requested transaction \(actionName) successfully")
    }
}
Answering a Call with CallKit:
1.	Construct a CXCallUpdate to describe the incoming call.
let incomingCallUpdate = CXCallUpdate()
incomingCallUpdate.remote handle = CXHandle(type: .phoneNumber, value: handle)
2.	Report the incoming call to the system.
provider.reportNewIncomingCall(with: uuid, update: incomingCallUpdate) { error in
    // Handle any errors
}
This action adds incoming calls to the app's call list if there are no errors and the call is allowed. You can check CXErrorCodeIncomingCallError for reasons if the call is denied.
Integrating EnableX APIs with CallKit:
Upon receiving an incoming call:
func report incoming calls(uuid: UUID, handle: String, hasVideo: Bool = true, completion: ((NSError?) -> Void)? = nil) {
    let update = CXCallUpdate()
    update.remote handle = CXHandle(type: .phoneNumber, value: handle)
    update.hasVideo = true
    
    provider.reportNewIncomingCall(with: uuid, update: update) { error in
        if error == nil {
            let call = EnxCall(uuid: uuid)
            call.handle = handle
            self.CallManager.add all(call)
        }
        completion?(error as NSError?)
    }
}
Generating an EnableX token and joining the room:
func join call(_ token: String) {
    let video size: NSDictionary = ["minWidth": 320, "minHeight": 180, "maxWidth": 1280, "max-height": 720]
    let room info: [String: Any] = ["allow_reconnect": true, "number_of_attempts": 3, "timeout_interval": 20, "activities": "View"]
    let localStreamInfo: NSDictionary = ["video": true, "audio": true, "data": true, "name": "Jay", "type": "public", "audio_only": false, "video size": videoSize]
    
    guard let stream = self.objection.join the room(token, delegate: self, publishStreamInfo: (localStreamInfo as! [AnyHashable: Any]), room info: room info, advance options: nil) else {
        return
    }
    
    self.localStream = stream
    self.localStream.delegate = self as EnxStreamDelegate
}
Handling room delegate notifications:
func room(_ room: EnxRoom?, didConnect roomMetadata: [AnyHashable: Any]?)
func room(_ room: EnxRoom?, didError reason: [Any]?)
Additional functionalities:
func room(_ room: EnxRoom?, didActiveTalkerView view: UIView?)
func room(_ room: EnxRoom?, didActiveTalkerList Data: [Any]?)
func didRoomDisconnect(_ response: [Any]?)

This guide should provide a comprehensive understanding of integrating app-to-app calling with CallKit and EnableX APIs while maintaining a native iOS UI experience. For a quick demonstration, refer to the provided fully functional **[CallKit code with EnableX API integration.](https://github.com/EnableX/Video-Audio-Calling-Using-CallKit)**