Push Notification

Push Notification is a feature that Notify the about the call.

Call PushNotification#

For enabling push notification for calls in IOS, you need to generate a VOIP Services Certificates in .p12 format with password. Once you have generated the VOIP certificate, share the .p12 along with its password to the server team for configuration in notificationservice.

The following screenshot shows the Audio Single Call PushNotification in Foreground and Background.

PushNotification

The following screenshot shows the Video Group Call PushNotification in Foreground and Background.

PushNotification

Add Notification Service Extension#

Notification Service extension is use to modify the contents of notifications before they are display and download the content

To obtain your Notification Service Extension you need to follow the below steps:

Step 1 : Create a new target for Notification Service with File > New > Target > Search Notification Service Extension, then Next.

PushNotification

Step 2 : When a dialog appears to activate the target extension, Give a name. For example: NotificationService, then click Finish.

PushNotification

Step 3 : The notification extension will come with a NotificationService.swift. Open it and replace the content with the code below:

import UserNotifications
import AVFoundation
import AudioToolbox
import MirrorFlySDK
import FlyUIKit
class NotificationService: UNNotificationServiceExtension {
let CONTAINER_ID = "**********************" //App group ID
let LICENSE_KEY = "**********************"
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
let payloadType = bestAttemptContent?.userInfo["type"] as? String
ChatManager.setAppGroupContainerId(id: CONTAINER_ID)
ChatManager.initializeSDK(licenseKey: LICENSE_KEY) { isSuccess, error, data in
if let bestAttemptContent = self.bestAttemptContent {
let payloadType = bestAttemptContent.userInfo["type"] as? String
if let notificationContent = request.content.mutableCopy() as? UNMutableNotificationContent {
FlyUIKitSDK.shared.intializePushNotification(notificationContent, isVoipType: false) { attemptContent in
contentHandler(attemptContent)
}
}
}
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}

Step 4 : Add App Groups to the target application and notification extension on the Signing & Capabilities tab.

Adding a Push Notification Capability#

To use push notifications, you are required to add the push notification capability to your Xcode project. Select your target and then choose Signing & Capabilities.

Enable all the below mentioned capabilities into your project.

Capabilities
App Groups
Background Modes
Push Notification

Now, go to the background mode and enable the below given modes

Background Modes
Voice over IP
Background Fetch
Remote Notification

App Group Id#

To receive messages in the background or in the killed state, You need to enable app group container ID. And pass in below method.

In order to access data between app and notification extension, enabling app group is mandatory to access user defaults and database in container.

Capabilities

To integrate and run Mirrorfly UIKit in your app, you need to initialize it first. Initialize the MirrorFlyUI instance through your view controller.

Note : Use below to configure SDK in AppDelegate.

ChatManager.setAppGroupContainerId(id: "XXXXXXXXXXXXXXXXXX")
ChatManager.initializeSDK(licenseKey: licenseKey) { isSuccess, error, data in
}

ViewController

Initialize Data for Call Push Notification#

In your project you must use the AppDelegate class within the didFinishLaunchingWithOptions method and also provide required data to build the Call Push Notification Builder. Let's have a look at the example given below.

import PushKit
clearPushNotifications()
registerForPushNotifications()
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { val, error in
}
)
application.registerForRemoteNotifications()
ChatManager.setMediaEncryption(isEnable: false)
ChatManager.hideNotificationContent(hide: false)
FlyUtils.setAppName(appName: "APP_NAME")
VOIPManager.sharedInstance.updateDeviceToken()

Add the method UNUserNotificationCenterDelegate and PKPushRegistryDelegate like below:

// MARK:- Push Notifications
@available(iOS 13.0, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
/// Register for APNS Notifications
func registerForPushNotifications() {
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
self.registerForVOIPNotifications()
}
}
}
}
/// This method is used to clear notifications and badge count
func clearPushNotifications() {
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
if token.count == 0 {
print("Push Status Credentials APNS:")
return;
}
print("#token appDelegate \(token)")
print("#token application DT => \(token)")
VOIPManager.sharedInstance.saveAPNSToken(token: token)
Utility.saveInPreference(key: "googleToken", value: token)
VOIPManager.sharedInstance.updateDeviceToken()
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Push didFailToRegisterForRemoteNotificationsWithError)")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([])
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
print("Push userInfo \(userInfo)")
completionHandler(.noData)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
guard let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else {
completionHandler()
return
}
if let rootVC = rootViewController as? UINavigationController{
let callLogsVC = MFUICallLogViewController()
rootVC.pushViewController(callLogsVC, animated: false)
}
}
}
// MARK:- VOIP Notifications
@available(iOS 13.0, *)
extension AppDelegate : PKPushRegistryDelegate {
func registerForVOIPNotifications() {
let pushRegistry = PKPushRegistry(queue: .main)
pushRegistry.delegate = self
pushRegistry.desiredPushTypes = [.voIP]
}
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
//print out the VoIP token. We will use this to test the nofications.
NSLog("VoIP Token: \(pushCredentials)")
let deviceTokenString = pushCredentials.token.reduce("") { $0 + String(format: "%02X", $1) }
print("#token pushRegistry VT => \(deviceTokenString)")
print(deviceTokenString)
VOIPManager.sharedInstance.saveVOIPToken(token: deviceTokenString)
Utility.saveInPreference(key: "voipToken", value: deviceTokenString)
VOIPManager.sharedInstance.updateDeviceToken()
}
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
NSLog("Push VOIP Received with Payload - %@",payload.dictionaryPayload)
print("#callopt \(FlyUtils.printTime()) pushRegistry voip received")
VOIPManager.sharedInstance.processPayload(payload.dictionaryPayload)
}
}

Custom Local PushNotification#

To get detail to customize the notification use local notification delegates LocalNotificationDelegate and MissedCallNotificationDelegate.

PushNotification

To customize local pushnotification refer and use below code in AppDelegate.

var notificationView: MFUICustomNotificationView?
var player: AVAudioPlayer?
@available(iOS 13.0, *)
extension AppDelegate : LocalNotificationDelegate {
func showOrUpdateOrCancelNotification(jid: String, chatMessage: ChatMessage, groupId: String) {
let myJid = try? FlyUtils.getMyJid()
print("#notification \(chatMessage.chatUserJid) \(groupId) \(chatMessage.senderUserJid)")
let current = UIApplication.shared.keyWindow?.getTopViewController()
if ChatManager.onGoingChatUserJid == chatMessage.senderUserJid || (ChatManager.onGoingChatUserJid == groupId && groupId != "") {
if !CallManager.isOngoingCall() {
self.playSound()
}
} else {
var title = "MirrorFly"
var userId = chatMessage.chatUserJid
if !groupId.isEmpty{
userId = chatMessage.senderUserJid
}
//let profileDetails = ChatManager.database.rosterManager.getContact(jid: userId)
let profileDetails = ContactManager.shared.getUserProfileDetails(for: userId)
let userName = FlyUtils.getUserName(jid: profileDetails?.jid ?? "0000000000", name: profileDetails?.name ?? "Fly User", nickName: profileDetails?.nickName ?? "Fly User", contactType: profileDetails?.contactType ?? .unknown)
title = userName
var message = chatMessage.messageTextContent
if chatMessage.isMessageRecalled == true {
message = "This message was deleted"
} else {
switch chatMessage.messageType{
case .text :
message = (message.count > 64) ? message : message
case .notification:
if chatMessage.messageChatType == .groupChat {
message = (message.count > 64) ? message : message
}
default :
message = chatMessage.messageType.rawValue.capitalized
}
}
var isCarbon = false
if FlyUIKitConstants.hideNotificationContent {
let (messageCount, chatCount) = ChatManager.getUNreadMessageAndChatCount()
var titleContent = emptyString()
if chatCount == 1{
titleContent = "\(messageCount) \(messageCount == 1 ? "message" : "messages")"
}else{
titleContent = "\(messageCount) messages from \(chatCount) chats"
}
title = (Bundle.main.displayName ?? "") + " (\(titleContent))"
message = "New Message"
}else{
if groupId.isEmpty{
title = userName
}else{
//let profileDetails = ChatManager.database.rosterManager.getContact(jid: groupId)
let profileDetails = ContactManager.shared.getUserProfileDetails(for: groupId)
title = "\(title) @ \(profileDetails?.name ?? "Fly Group ")"
}
}
if chatMessage.senderUserJid == myJid {
isCarbon = true
}
if isCarbon {
message = "Duplicate message"
}
if !chatMessage.mentionedUsersIds.isEmpty {
message = ChatUtils.getMentionTextContent(message: message, isMessageSentByMe: chatMessage.isMessageSentByMe, mentionedUsers: chatMessage.mentionedUsersIds).string
}
executeOnMainThread {
self.showCustomNotificationView(title: title , message: message, chatMessage: chatMessage)
}
}
}
}
@available(iOS 13.0, *)
extension AppDelegate : MissedCallNotificationDelegate {
func onMissedCall(isOneToOneCall: Bool, userJid: String, groupId: String?, callType: String, userList: [String]) {
let current = UIApplication.shared.keyWindow?.getTopViewController()
if (current is MFUICallScreenViewController) {
//Application Badge Count
var appBadgeCount = UIApplication.shared.applicationIconBadgeNumber
appBadgeCount = appBadgeCount - CallLogManager.getUnreadMissedCallCount()
UIApplication.shared.applicationIconBadgeNumber = appBadgeCount
//CallLogs Badge Count
CallLogManager.resetUnreadMissedCallCount()
NotificationCenter.default.post(name: NSNotification.Name("updateUnReadMissedCallCount"), object: CallLogManager.getUnreadMissedCallCount())
}
var callMode = ""
if CallManager.getTempRoomID() == nil {
callMode = (userList.count > 1) ? "group " : ""
}else{
callMode = CallManager.getTempIsGroupCall() ? "group " : ""
}
let conjuction = (callMode == "group " || callType == "video call") ? " a " : " an "
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
self.showCustomNotificationView(title: CallManager.getNameString() , message: "You missed" + conjuction + callMode + callType, chatMessage: "media-call")
})
}
}
@available(iOS 13.0, *)
extension AppDelegate {
func showCustomNotificationView(title: String, message: String, chatMessage: Any? = nil) {
if self.notificationView != nil {
self.notificationView?.removeFromSuperview()
}
let window = UIApplication.shared.keyWindow!
let view = MFUICustomNotificationView()
view.frame = CGRect(x: 10, y: window.safeAreaInsets.top - 130, width: (window.bounds.width) - 20, height: 70)
view.titleLabel.text = title
view.messageLabel.text = message
view.accessibilityElements = [chatMessage as Any]
view.logoImg.applyShadow(1.0, shawdowOpacity: 0.2)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
tap.numberOfTapsRequired = 1
view.addGestureRecognizer(tap)
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(handleUpSwipe(_:)))
swipeUp.direction = UISwipeGestureRecognizer.Direction.up
view.addGestureRecognizer(swipeUp)
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowRadius = 3.0
view.layer.shadowOpacity = 0.2
view.layer.shadowOffset = CGSize(width: 0, height: 1)
view.layer.masksToBounds = false
window.addSubview(view)
notificationView = view
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if !CallManager.isOngoingCall() {
self.playSound()
if CommonDefaults.vibrationEnable {
AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))
}
}
}
UIView.animate(withDuration: 0.5) {
view.frame = CGRect(x: 10, y: window.safeAreaInsets.top, width: (window.bounds.width) - 20, height: 70)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
UIView.animate(withDuration: 0.3, delay: 0, options: .transitionFlipFromTop, animations: {
view.frame = CGRect(x: 10, y: window.safeAreaInsets.top - 130, width: (window.bounds.width) - 20, height: 70)
},completion: {_ in })
})
}
@objc func handleUpSwipe(_ recognizer: UISwipeGestureRecognizer) {
print("Swiped on a Notification View")
UIView.animate(withDuration: 0.3, delay: 0, options: .transitionFlipFromTop, animations: {
guard let window = UIApplication.shared.keyWindow else {
return
}
self.notificationView?.frame = CGRect(x: 10, y: window.safeAreaInsets.top - 130, width:window.frame.width - 20, height: 70)
},completion: {_ in
self.notificationView?.removeFromSuperview()
})
}
@objc func handleTap(_ sender: UITapGestureRecognizer?) {
UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true)
let current = UIApplication.shared.keyWindow?.getTopViewController()
let myJid = try? FlyUtils.getMyJid()
// if (current is ProfileViewController) {
// return
// }
if (current is MFUICallScreenViewController) {
(current as! MFUICallScreenViewController).showCallOverlay()
}
//Redirect to chat page
if let message = (sender?.view?.accessibilityElements as? [ChatMessage])?.first {
print("Tap on a Notification View \(message)")
if ChatManager.getContact(jid: myJid ?? "")?.isBlockedByAdmin ?? false {
// navigateToBlockedScreen()
} else {
let messageId = message.messageId
if let message = FlyMessenger.getMessageOfId(messageId: messageId) {
pushChatId = message.chatUserJid
if !CommonDefaults.showAppLock {
pushChatId = nil
navigateToChatScreen(chatId: message.chatUserJid , message: message, completionHandler: {})
}
}
}
}
//Redirect to call-logs page
if let message = sender?.view?.accessibilityElements as? [String] {
print("Tap on a Call View \(message)")
pushChatId = "media-call"
if ChatManager.getContact(jid: myJid ?? "")?.isBlockedByAdmin ?? false {
// navigateToBlockedScreen()
} else {
let navigationController : UINavigationController
let initialViewController = MFUIRecentChatListViewController()
navigationController = UINavigationController(rootViewController: initialViewController)
UIApplication.shared.keyWindow?.rootViewController = navigationController
UIApplication.shared.keyWindow?.makeKeyAndVisible()
}
}
self.notificationView?.removeFromSuperview()
}
func playSound() {
if !(CommonDefaults.selectedNotificationSoundName[NotificationSoundKeys.name.rawValue]?.contains("None") ?? false) && CommonDefaults.notificationSoundEnable {
guard let path = Bundle.main.path(forResource: "notification-tone", ofType:"mp3") else {
return }
let url = URL(fileURLWithPath: path)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.soloAmbient, options: AVAudioSession.CategoryOptions.mixWithOthers)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url)
player?.play()
player?.volume = 1.0
} catch let error {
print(error.localizedDescription)
}
}
}
func navigateToChatScreen(chatId : String, message : ChatMessage,completionHandler: @escaping () -> Void){
let myJid = try? FlyUtils.getMyJid()
var dismisLastViewController = false
let recentChatListViewController = MFUIRecentChatListViewController()
// recentChatListViewController.isInitialLoading = true
UIApplication.shared.keyWindow?.rootViewController = UINavigationController(rootViewController: recentChatListViewController)
UIApplication.shared.keyWindow?.makeKeyAndVisible()
if let rootVC = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController{
if let currentVC = rootVC.children.last, currentVC.isKind(of: MFUIChatViewParentController.self){
dismisLastViewController = true
}
if dismisLastViewController{
rootVC.popViewController(animated: false)
}
if let profileDetails = ContactManager.shared.getUserProfileDetails(for: chatId) , (chatId) != myJid {
let vc = MFUIChatViewParentController(chatMessage: message, chatJid: chatId, messageMenItem: .reply)
vc.showLoading(false, isContact: false)
rootVC.removeViewController(MFUIChatViewParentController.self)
rootVC.pushViewController(vc, animated: false)
}
}
}
}
extension UIWindow {
func getTopViewController() -> UIViewController? {
var top = self.rootViewController
while true {
if let presented = top?.presentedViewController {
top = presented
} else if let nav = top as? UINavigationController {
top = nav.visibleViewController
} else if let tab = top as? UITabBarController {
top = tab.selectedViewController
} else {
break
}
}
if let mainNC = top as? UINavigationController ,let mainVC = mainNC.visibleViewController {
return mainVC
}
return top
}
}

PushNotification