Notifications for incoming call and call on hold/unhold
Categories:
Handling Incoming Calls and Call Hold/Unhold Notifications in iOS
Learn how to detect and respond to incoming calls, as well as call hold and unhold events, in your iOS applications using Objective-C. This guide covers the necessary frameworks and event handling mechanisms.
Developing robust iOS applications often requires interaction with the device's core functionalities, including telephony. For apps that manage audio, video, or real-time communication, it's crucial to properly handle system-level events like incoming calls or changes in call status (hold/unhold). Failing to do so can lead to a poor user experience, such as audio interruptions or unexpected application behavior. This article will guide you through the process of setting up your Objective-C project to receive and respond to these critical call notifications.
Understanding CallKit and Core Telephony Frameworks
Prior to iOS 10, handling call events primarily relied on the Core Telephony framework. While still relevant for some basic status checks, Apple introduced CallKit (CXProvider, CXCallObserver) in iOS 10 to provide a more robust and secure way for VoIP apps to integrate with the system's phone UI and manage calls. For detecting general incoming calls and changes in call status for the device's native phone app, Core Telephony remains the go-to framework. We'll focus on Core Telephony for detecting system-wide call events, which is applicable for iOS 7 and later.
flowchart TD A[iOS System] --> B{Incoming Call Event} B --> C{CTCallCenter Notification} C --> D[App Delegate/Observer] D --> E{Handle Call State Change} E --> F{Update UI/Pause Audio} B --> G{Call Hold/Unhold Event} G --> C
Flowchart of Call Event Handling in iOS
Detecting Incoming Calls with CTCallCenter
The CTCallCenter
class from the Core Telephony framework is your primary tool for monitoring call events. You can instantiate a CTCallCenter
object and set its callEventHandler
block. This block will be invoked whenever a call state changes, providing you with a CTCall
object that contains information about the current call, including its state.
#import <CoreTelephony/CTCallCenter.h>
#import <CoreTelephony/CTCall.h>
@interface CallObserver : NSObject
@property (nonatomic, strong) CTCallCenter *callCenter;
@end
@implementation CallObserver
- (instancetype)init {
self = [super init];
if (self) {
_callCenter = [[CTCallCenter alloc] init];
__weak typeof(self) weakSelf = self;
_callCenter.callEventHandler = ^(CTCall* call) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
if ([call.callState isEqualToString:CTCallStateIncoming]) {
NSLog(@"Incoming call detected.");
// Handle incoming call: pause audio, show UI, etc.
} else if ([call.callState isEqualToString:CTCallStateDialing]) {
NSLog(@"Call dialing.");
} else if ([call.callState isEqualToString:CTCallStateConnected]) {
NSLog(@"Call connected.");
} else if ([call.callState isEqualToString:CTCallStateDisconnected]) {
NSLog(@"Call disconnected.");
// Resume audio, hide UI, etc.
}
};
}
return self;
}
@end
Initializing CTCallCenter and handling call state changes.
CTCallCenter
instance (e.g., as a property in your AppDelegate or a dedicated observer class) to ensure the callEventHandler
remains active and receives notifications.Detecting Call Hold/Unhold Status
The CTCall
object, passed to your callEventHandler
block, provides the callState
property. While CTCallStateIncoming
, CTCallStateConnected
, and CTCallStateDisconnected
are standard states, detecting a call being put on hold or taken off hold requires a slightly different approach. The CTCall
object itself doesn't directly expose a 'hold' state. Instead, you typically infer this by observing changes in the CTCallStateConnected
state in conjunction with other audio session interruptions or by managing your own call state within your application if you are the one initiating the hold.
For applications that manage their own VoIP calls, you would implement hold/unhold logic within your CXProvider
delegate methods (if using CallKit) or your custom VoIP stack. However, for detecting when the native phone app puts a call on hold, you'll primarily rely on the CTCallStateConnected
state and potentially observe audio session interruptions. When a call is put on hold, the audio session might be interrupted or reconfigured, which can be observed using AVAudioSession
notifications.
// Example of observing AVAudioSession interruptions (simplified)
// This is a general approach and might not directly map to 'hold' state
- (void)registerForAudioSessionNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAudioSessionInterruption:)
name:AVAudioSessionInterruptionNotification
object:nil];
}
- (void)handleAudioSessionInterruption:(NSNotification *)notification {
if ([notification.userInfo[AVAudioSessionInterruptionTypeKey] integerValue] == AVAudioSessionInterruptionTypeBegan) {
NSLog(@"Audio session interruption began.");
// Potentially a call going on hold or another app taking audio
} else if ([notification.userInfo[AVAudioSessionInterruptionTypeKey] integerValue] == AVAudioSessionInterruptionTypeEnded) {
NSLog(@"Audio session interruption ended.");
// Potentially a call coming off hold
if ([notification.userInfo[AVAudioSessionInterruptionOptionKey] integerValue] == AVAudioSessionInterruptionOptionShouldResume) {
// Resume audio playback if appropriate
}
}
}
// Don't forget to unregister in dealloc
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVAudioSessionInterruptionNotification
object:nil];
}
Observing AVAudioSession interruptions for potential hold/unhold events.
AVAudioSessionInterruptionNotification
can indicate audio changes related to calls, it's a general notification. It doesn't specifically confirm a 'hold' state but rather that your app's audio session has been interrupted or restored. Combine this with CTCallCenter
observations for a more complete picture.Best Practices and Considerations
When integrating call handling into your app, keep the following best practices in mind:
- Thread Safety:
callEventHandler
is typically called on a background thread. Ensure any UI updates or interactions with your app's main data model are dispatched to the main thread. - Audio Session Management: Properly configure and react to changes in your
AVAudioSession
. When an incoming call occurs, your app's audio should typically pause. When the call ends, you might want to resume audio. - Permissions: Core Telephony does not require explicit user permissions, but if your app interacts with CallKit for VoIP features, you will need appropriate entitlements and user consent.
- State Management: Maintain an internal state for your application's audio or communication features. This state should be updated based on call events to ensure consistent behavior.
- Testing: Thoroughly test your app's behavior with various call scenarios: incoming calls, outgoing calls, calls on hold, multiple calls, and calls ending unexpectedly.
1. Add Core Telephony Framework
In your Xcode project, go to your target's 'General' settings, then 'Frameworks, Libraries, and Embedded Content'. Click the '+' button and add CoreTelephony.framework
.
2. Create a Call Observer Class
Create a new Objective-C class (e.g., CallObserver
) to encapsulate your CTCallCenter
logic. This helps keep your AppDelegate
clean.
3. Initialize and Retain CTCallCenter
In your CallObserver
's init
method, create an instance of CTCallCenter
and assign its callEventHandler
block. Make sure to retain this CallObserver
instance somewhere accessible, like in your AppDelegate
.
4. Implement Call State Logic
Inside the callEventHandler
block, use if/else if
statements to check call.callState
and implement your app's specific logic for incoming, connected, and disconnected calls. Consider AVAudioSession
notifications for more nuanced audio handling.
5. Test Thoroughly
Run your app on a physical device and simulate various call scenarios to ensure your app responds correctly to all call events.