Introducing "NAMO" Real-Time Speech AI Model: On-Device & Hybrid Cloud 📢PRESS RELEASE

CallKit: A Comprehensive Guide for iOS VoIP App Development

A detailed guide to Apple's CallKit framework for iOS developers. Learn how to integrate CallKit into your VoIP apps for a seamless calling experience.

Introduction to CallKit

What is CallKit?

CallKit is an Apple framework introduced in iOS 10 that allows VoIP (Voice over Internet Protocol) apps to integrate seamlessly with the native iOS phone experience. Instead of operating as a separate entity, your VoIP app's calls appear and behave just like regular cellular calls.

Why Use CallKit?

Using CallKit provides numerous benefits:
  • Improved User Experience: Users can answer VoIP calls from the lock screen, manage them using the built-in phone UI, and access call history in the Phone app.
  • System Integration: CallKit integrates with features like Do Not Disturb, Call Waiting, and CarPlay.
  • Enhanced Reliability: CallKit ensures proper call prioritization and resource management, leading to more reliable call handling.
  • Familiar Interface: Users are already familiar with the native iOS call interface, reducing the learning curve for your VoIP app.

Key Features of CallKit

CallKit unlocks a powerful set of features for iOS VoIP applications:
  • Incoming and Outgoing Call Management: Handles the presentation and management of both incoming and outgoing calls through the native iOS interface.
  • Call Waiting and Holding: Supports putting calls on hold and managing multiple concurrent calls.
  • Call Blocking and Identification: Integrates with iOS's call blocking and identification features.
  • Contact Integration: Displays caller information from the user's contacts.
  • CarPlay Support: Allows users to make and receive VoIP calls through CarPlay.
  • Call Directory Extension: Enables your app to identify and filter incoming calls, marking potential spam or unwanted callers.

CallKit Architecture and Core Components

The CallKit framework revolves around several key components that work together to manage call interactions. Understanding these components is crucial for successful CallKit integration. The CXProvider is the central element, acting as the interface between your application and the system's call management. CXCallController is used to request changes to the call state, like starting and ending calls. Finally, CXCallObserver lets you monitor the call state.
1sequenceDiagram
2    participant Your App
3    participant CXCallController
4    participant CXProvider
5    participant iOS System
6
7    Your App->>CXCallController: Initiate Outgoing Call (Request)
8    CXCallController->>CXProvider: Report Begin Call Transaction
9    CXProvider->>iOS System: Inform System about New Call
10    iOS System-->>CXProvider: Request Actions (e.g., Answer, End Call)
11    CXProvider->>Your App: Delegate Actions to Your App
12    Your App->>iOS System: Handle Audio and Signaling
13    Your App->>CXProvider: Report Call Events (Connected, Ended)
14    CXProvider->>iOS System: Update Call State in System UI

CXProvider: The Heart of CallKit

The CXProvider object is the central point of contact between your app and the CallKit framework. It's responsible for reporting call state changes to the system and receiving actions from the system, such as answering or ending a call. You need to create and configure a CXProvider instance to represent your VoIP service.
1import CallKit
2
3let providerConfiguration = CXProviderConfiguration(localizedName: "My VoIP App")
4providerConfiguration.supportsVideo = true
5providerConfiguration.maximumCallsPerCallGroup = 1
6
7let provider = CXProvider(configuration: providerConfiguration)
8provider.setDelegate(self, queue: nil)
This code snippet demonstrates the basic setup of a CXProvider. It initializes a CXProviderConfiguration to define the properties of your VoIP service, such as its localized name and whether it supports video calls. The CXProvider is then initialized with this configuration, and a delegate is set to handle incoming call-related events.

CXCallController: Managing Call Interactions

The CXCallController is used to request changes to the call state, such as starting an outgoing call or ending an existing call. You use transactions to bundle multiple actions together, ensuring that they are executed atomically.
1import CallKit
2
3let callController = CXCallController()
4
5func startCall(handle: String) {
6    let handle = CXHandle(type: .generic, value: handle)
7    let startCallAction = CXStartCallAction(call: UUID(), handle: handle)
8
9    let transaction = CXTransaction()
10transaction.addAction(startCallAction)
11
12    callController.request(transaction) { error in
13        if let error = error {
14            print("Error starting call: \(error.localizedDescription)")
15        } else {
16            print("Call started successfully")
17        }
18    }
19}
This snippet shows how to initiate an outgoing call using the CXCallController. A CXStartCallAction is created with the recipient's handle, and this action is added to a CXTransaction. The CXCallController then executes this transaction, notifying the system that an outgoing call is being initiated.

CXCallObserver: Monitoring Call Activity

The CXCallObserver allows your app to monitor call activity, such as changes in call state or the addition or removal of calls. This is useful for updating your app's UI to reflect the current call status.
1import CallKit
2
3let callObserver = CXCallObserver()
4
5func observeCallChanges() {
6    callObserver.setDelegate(self, queue: nil)
7}
8
9extension YourViewController: CXCallObserverDelegate {
10    func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
11        print("Call state changed for call with UUID: \(call.uuid)")
12        print("Call hasConnected: \(call.hasConnected)")
13        print("Call isOutgoing: \(call.isOutgoing)")
14        // Update UI based on call state
15    }
16}
This code demonstrates how to use CXCallObserverDelegate to observe call state changes and get notified when the properties of any CXCall change. This delegate provides a means to react to any change related to any of your calls.

Integrating CallKit into Your iOS App

Integrating CallKit involves several steps, from setting up your project to handling incoming and outgoing calls. Let's break down the process.

Setting up Your Project

First, you need to enable the CallKit capability in your Xcode project:
  1. Open your project in Xcode.
  2. Select your target in the Project navigator.
  3. Go to the "Signing & Capabilities" tab.
  4. Click the "+ Capability" button.
  5. Search for and add the "CallKit" capability.
This ensures that your app has the necessary entitlements to use the CallKit framework.

Requesting CallKit Capabilities

You must request permission to use CallKit by including the NSVoIPBackgroundUsage key in your app's Info.plist file. This key explains to the user why your app needs VoIP capabilities. Without this, CallKit may not function correctly.
Additionally, ensure your app has microphone permissions. If you want to use push notifications to indicate incoming calls, you need to request push notification permissions and set up your provisioning profiles for push notifications.

Handling Incoming Calls

When an incoming call arrives, CallKit will notify your app through the CXProviderDelegate methods. You need to implement these methods to respond to the incoming call, display the call UI, and establish the VoIP connection.
1import CallKit
2
3extension YourProviderDelegate: CXProviderDelegate {
4    func providerDidReset(_ provider: CXProvider) {
5        // Handle provider reset (e.g., after a crash)
6    }
7
8    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
9        // Answer the call
10        action.fulfill()
11    }
12
13    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
14        // End the call
15        action.fulfill()
16    }
17
18    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
19            // Start the outgoing call
20            action.fulfill()
21        }
22
23    func provider(_ provider: CXProvider, didReceive incomingCall: CXCallUpdate) {
24        // Handle incoming call
25       if let handle = incomingCall.remoteHandle {
26            incomingCall.answerCompletion(completion: { success in
27                if success {
28                    // Successfully answered call
29                } else {
30                    // Failed to answer call
31                }
32            })
33        } else {            
34        }
35    }
36
37}
In this example, the provider(_:didReceive:) function is crucial for handling incoming calls. When a call is received, CallKit provides a CXCallUpdate object that contains information about the call, such as the caller's handle (phone number or username). You must configure your CXProvider with the incoming call, and then fulfill or fail the action to indicate success or failure. You would also handle actions to answer and end the call.

Handling Outgoing Calls

To initiate an outgoing call, you use the CXCallController to create a CXStartCallAction and execute it in a transaction. This informs CallKit that your app is initiating a call, and the system will display the native call UI.
1import CallKit
2
3let callController = CXCallController()
4
5func startCall(phoneNumber: String) {
6    let handle = CXHandle(type: .phoneNumber, value: phoneNumber)
7    let startCallAction = CXStartCallAction(call: UUID(), handle: handle)
8
9    let transaction = CXTransaction()
10transaction.addAction(startCallAction)
11
12    callController.request(transaction) { error in
13        if let error = error {
14            print("Error starting call: \(error.localizedDescription)")
15        } else {
16            print("Call started successfully")
17        }
18    }
19}
This code snippet demonstrates how to initiate an outgoing call using CXCallController. It creates a CXStartCallAction with the recipient's phone number and executes it in a transaction, thus notifying the system that an outgoing call is being initiated.

Advanced CallKit Techniques

Beyond basic call handling, CallKit offers several advanced features that can enhance your VoIP app.

Managing Multiple Calls

CallKit supports managing multiple concurrent calls, allowing users to put calls on hold and switch between them. You can use the CXProvider's reportCall methods to update the call state and inform the system about call holding and switching.

Integrating with Push Notifications

Push notifications can be used to alert users of incoming calls when your app is in the background. You need to configure your app to receive push notifications and use the PKPushRegistry API to register for VoIP push notifications. When a VoIP push notification arrives, you can use the information in the notification to create a CXCallUpdate and report it to the CXProvider.

Handling CallKit Errors and Edge Cases

It's important to handle potential errors and edge cases in your CallKit integration. This includes handling situations where a call fails to connect, the user declines a call, or the network connection is lost. Implement error handling and logging to identify and address these issues.

Implementing Call Directory Integration

Call Directory integration allows your app to identify and filter incoming calls, marking potential spam or unwanted callers. You need to create a Call Directory extension and provide a list of phone numbers to block or identify. This extension runs in the background and intercepts incoming calls, providing a valuable service to your users.

Best Practices and Considerations

When developing with CallKit, consider these best practices for security, performance, and user experience.

Security and Privacy

  • Secure VoIP Communication: Use encryption to protect the privacy of your users' calls. Implement TLS/SSL for secure signaling and SRTP/ZRTP for secure media transmission.
  • Handle User Data Responsibly: Be transparent about how you collect and use user data, and comply with privacy regulations like GDPR.

Performance Optimization

  • Minimize Battery Consumption: Optimize your app's code to reduce battery drain during calls. Use efficient audio codecs and minimize background activity.
  • Handle Network Connectivity Gracefully: Implement robust error handling to deal with network connectivity issues. Use techniques like packet loss concealment to improve audio quality in poor network conditions.

User Experience

  • Provide Clear Call Status Information: Display clear and concise information about the call status in your app's UI. This includes information about the caller, call duration, and call quality.
  • Ensure Seamless Integration with Native UI: Strive for a seamless integration with the native iOS call UI. Use the same icons and terminology as the Phone app to avoid confusing users.

Conclusion

CallKit is a powerful framework that enables iOS developers to create seamless and integrated VoIP experiences. By understanding the core components, following best practices, and addressing potential issues, you can build robust and user-friendly VoIP apps that leverage the full potential of CallKit. Remember to consult Apple's official documentation and explore available resources to deepen your understanding and troubleshoot any challenges you may encounter.

Get 10,000 Free Minutes Every Months

No credit card required to start.

Want to level-up your learning? Subscribe now

Subscribe to our newsletter for more tech based insights

FAQ