Let's integrate our UIKIT in few minutes
Notification Configuration#
iOS configuration#
Step 1: Open Info.plist
file and add the notification related code as mentioned below.
...
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
<string>voip</string>
</array>
...
Step 2: Enable the following options to receive push notification when app is in background/killed.
- Open Xcode -> Signing & Capabilities -> Background Modes. Make sure you enabled the following checkboxes
Step 3: Go to Xcode -> Signing & Capabilities. Click + Capabality
on the top left corner of the sub window.
And serach for Push Notification
and add it.
Note: Ignore this step if you already have
Push Notificiation
option.
Step 4: Create the bridge header file like <PROJECT_NAME>-Bridging-Header.h file.
Use this file to import your target's public headers that you would like to expose to Swift.
#import "RNCallKeep.h"
#import "RNKeyEvent.h"
Step 5: Open AppDelegate
and add the below mentioned codes.
- Swift
- Objective-C
...
// add the below imports
import CallKit
import RNVoipPushNotification
import UserNotifications
...
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
...
// VoIP PushKit registration
RNVoipPushNotificationManager.voipRegistration() // <- add this line just before the below line
...
}
// MARK: - VoIP PushKit
// VoIP configuration section starts here
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
RNVoipPushNotificationManager.didUpdate(pushCredentials, forType: type.rawValue)
}
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
// --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token.
}
// --- Handle incoming pushes
func pushRegistry(
_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: @escaping () -> Void
) {
// --- NOTE: apple forced us to invoke callkit ASAP when we receive voip push
// --- see: react-native-callkeep
// --- Retrieve information from your voip push payload
let uuid = UUID().uuidString
let payloadDict = payload.dictionaryPayload
let handle = payloadDict["caller_id"] as? String ?? "unknown@mirrorfly"
let callerId = handle.components(separatedBy: "@").first ?? "unknown"
let callerName = payloadDict["caller_name"] as? String ?? callerId
let hasVideoValue = payloadDict["call_type"] as? String ?? "audio"
let hasVideo = (hasVideoValue == "video")
let callObserver = CXCallObserver()
let activeCallCount = callObserver.calls.count
// --- this is optional, only required if you want to call `completion()` on the js side
RNVoipPushNotificationManager.addCompletionHandler(uuid, completionHandler: completion)
if activeCallCount < 1 {
// Check the Mic permission and if the permission is not provided then end the call
RNCallKeep.reportNewIncomingCall(
uuid,
handle: callerId,
handleType: "generic",
hasVideo: hasVideo,
localizedCallerName: callerName,
supportsHolding: true,
supportsDTMF: true,
supportsGrouping: true,
supportsUngrouping: true,
fromPushKit: true,
payload: payloadDict,
withCompletionHandler: completion
)
if hasVideo ? !checkVideoPermission() : !checkAudioPermission() {
RNCallKeep.endCall(withUUID: uuid, reason: 1)
// Showing local notification for the ended incoming call
let content = UNMutableNotificationContent()
content.title = callerId
content.body = "You missed \(hasVideo ? "a" : "an") \(hasVideo ? "video call" : "audio call"). Please enable permission in App Settings."
content.sound = .default
let request = UNNotificationRequest(identifier: "ImmediateNotification", content: content, trigger: nil)
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Notification error: \(error)")
}
}
}
} else {
// --- sending the received push to JS for the incoming call (while the user is already on a call) to send busy status to the caller
RNVoipPushNotificationManager.didReceiveIncomingPush(with: payload, forType: type.rawValue)
}
// --- You don't need to call it if you stored `completion()` and will call it on the js side.
completion()
}
// MARK: - Permissions
func checkAudioPermission() -> Bool {
let status = AVCaptureDevice.authorizationStatus(for: .audio)
if status != .authorized {
if status == .notDetermined {
AVCaptureDevice.requestAccess(for: .audio) { _ in }
}
return false
}
return true
}
func checkVideoPermission() -> Bool {
let micStatus = AVCaptureDevice.authorizationStatus(for: .audio)
let videoStatus = AVCaptureDevice.authorizationStatus(for: .video)
if videoStatus != .authorized || micStatus != .authorized {
if videoStatus == .notDetermined {
AVCaptureDevice.requestAccess(for: .video) { _ in }
}
if micStatus == .notDetermined {
AVCaptureDevice.requestAccess(for: .audio) { _ in }
}
return false
}
return true
}
// VoIP configuration section ends here
@end
...
// add the below imports
#import <PushKit/PushKit.h>
#import <RNVoipPushNotificationManager.h>
#import "RNCallKeep.h"
#import <CallKit/CallKit.h>
#import <RNKeyEvent.h>
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
...
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
[RNVoipPushNotificationManager voipRegistration]; // <- add this line just before the below line
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
...
}
// add the below mentioned codes just before the last `@end` line of the file.
// VoIP configuration section starts here
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type
{
[RNVoipPushNotificationManager didUpdatePushCredentials:pushCredentials forType:(NSString *)type];
}
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{
// --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token.
}
// --- Handle incoming pushes
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)())completion
{
// --- NOTE: apple forced us to invoke callkit ASAP when we receive voip push
// --- see: react-native-callkeep
// --- Retrieve information from your voip push payload
NSString *uuid = [[NSUUID UUID] UUIDString];
NSString *callerName = payload.dictionaryPayload[@"caller_name"];
NSString *handle = payload.dictionaryPayload[@"caller_id"];
NSString *callerId = [[handle componentsSeparatedByString:@"@"] objectAtIndex:0];
NSString *hasvideoValue = payload.dictionaryPayload[@"call_type"];
BOOL hasvideo = [hasvideoValue isEqualToString:@"video"] ? YES : NO;
CXCallObserver *callObserver = [[CXCallObserver alloc] init];
NSInteger count = callObserver.calls.count;
// --- this is optional, only required if you want to call `completion()` on the js side
[RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion];
if(count < 1) {
// Check the Mic permission and if the permission is not provided then end the call
[RNCallKeep reportNewIncomingCall: uuid
handle: callerId
handleType: @"generic"
hasVideo: hasvideo
localizedCallerName: callerId
supportsHolding: YES
supportsDTMF: YES
supportsGrouping: YES
supportsUngrouping: YES
fromPushKit: YES
payload: [payload dictionaryPayload]
withCompletionHandler: completion];
if (hasvideo ? !self.checkVideoPermission : !self.checkAudioPermission) {
[RNCallKeep endCallWithUUID:uuid reason:1]; // ending the call with reason Failed
// Showing local notification for the ended incoming call
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = callerId;
content.body = [NSString stringWithFormat:@"You missed %@ %@. Please enable permission in App Settings to help you connect with your friends.", hasvideo ? @"a" : @"an", hasvideo ? @"video call" : @"audio call"];
content.sound = [UNNotificationSound defaultSound];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"ImmediateNotification"
content:content
trigger:nil];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Error scheduling local notification: %@", error);
}
}];
}
}else{
// --- sending the received push to JS for the incoming call (while the user is already on a call) to send busy status to the caller
[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
}
// --- You don't need to call it if you stored `completion()` and will call it on the js side.
completion();
}
- (BOOL)checkAudioPermission{
AVAuthorizationStatus micPermissionStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
if(micPermissionStatus != AVAuthorizationStatusAuthorized) {
if (micPermissionStatus == AVAuthorizationStatusNotDetermined) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {}];
}
return NO;
}else{
return YES;
}
}
- (BOOL)checkVideoPermission{
AVAuthorizationStatus micPermissionStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
AVAuthorizationStatus videoPermissionStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(videoPermissionStatus != AVAuthorizationStatusAuthorized || micPermissionStatus != AVAuthorizationStatusAuthorized) {
if (videoPermissionStatus == AVAuthorizationStatusNotDetermined) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {}];
}
if (micPermissionStatus == AVAuthorizationStatusNotDetermined) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {}];
}
return NO;
}else{
return YES;
}
}
// VoIP configuration section ends here
@end