4

I am unable to fire off the call kit UI incoming call from app delegate. How do I do this? I tried the speaker box example but it didn't help. When i run the reportIncomingCall method within ViewController it works. When i ru reportIncomingCall in AppDelegate it doesn't work. I need it to run in Appdelegate so i can send VoIP notification to report incoming call.

here is my app delegate:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate {

    class var shared: AppDelegate {
        return UIApplication.shared.delegate as! AppDelegate
    }

    var window: UIWindow?

    var providerDelegate: ViewController?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        return true
    }

    func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {

        //register for voip notifications
        let voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
        voipRegistry.desiredPushTypes = Set([PKPushType.voIP])
        voipRegistry.delegate = self;
    }

    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenForType type: PKPushType) {

         NSLog("token invalidated")

    }

    func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, forType type: PKPushType) {

        //print out the VoIP token. We will use this to test the notification.
        NSLog("voip token: \(credentials.token)")

        print("didUpdatePushCredentials: %@ - Type: %@", credentials.token, type)
        var token: String = ""
        for i in 0..<credentials.token.count {
            token += String(format: "%02.2hhx", credentials.token[i] as CVarArg)
        }

        print(token)

    }

    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) {

        print("notification receivd!")
        guard type == .voIP else { return }

        let uuidString = payload.dictionaryPayload["UUID"] as? String!
        let roomName = payload.dictionaryPayload["roomName"] as? String!
        print("uuid", uuidString!)
        print("roomName", roomName!)


        providerDelegate?.performStartCallAction(uuid: UUID(), roomName: "Test")

    }

    //Intents
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
        guard let viewController = window?.rootViewController as? ViewController, let interaction = userActivity.interaction else {
            return false
        }

        var personHandle: INPersonHandle?

        if let startVideoCallIntent = interaction.intent as? INStartVideoCallIntent {
            personHandle = startVideoCallIntent.contacts?[0].personHandle
        } else if let startAudioCallIntent = interaction.intent as? INStartAudioCallIntent {
            personHandle = startAudioCallIntent.contacts?[0].personHandle
        }

        if let personHandle = personHandle {
            viewController.performStartCallAction(uuid: UUID(), roomName: personHandle.value)
        }

        return true
    }

}

and in the ViewController class:

extension ViewController : CXProviderDelegate {

    func providerDidReset(_ provider: CXProvider) {
        logMessage(messageText: "providerDidReset:")

        localMedia?.audioController.stopAudio()
    }

    func providerDidBegin(_ provider: CXProvider) {
        logMessage(messageText: "providerDidBegin")
        //_ = Timer.scheduledTimerWithTimeInterval(15, target: self, selector: #selector(ViewController.expireCall), userInfo: nil, repeats: false)
    }

    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        logMessage(messageText: "provider:didActivateAudioSession:")

        localMedia?.audioController.startAudio()
    }

    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        logMessage(messageText: "provider:didDeactivateAudioSession:")
    }

    func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
        logMessage(messageText: "provider:timedOutPerformingAction:")
    }

    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
        logMessage(messageText: "provider:performStartCallAction:")

        localMedia?.audioController.configureAudioSession(.videoChatSpeaker)

        callKitProvider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)

        performRoomConnect(uuid: action.callUUID, roomName: action.handle.value)

        // Hang on to the action, as we will either fulfill it after we succesfully connect to the room, or fail
        // it if there is an error connecting.
        pendingAction = action
    }

    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        logMessage(messageText: "provider:performAnswerCallAction:")

        // NOTE: When using CallKit with VoIP pushes, the workaround from https://forums.developer.apple.com/message/169511
        //       suggests configuring audio in the completion block of the `reportNewIncomingCallWithUUID:update:completion:`
        //       method instead of in `provider:performAnswerCallAction:` per the Speakerbox example.
        // localMedia?.audioController.configureAudioSession()

        performRoomConnect(uuid: action.callUUID, roomName: self.roomTextField.text)

        // Hang on to the action, as we will either fulfill it after we succesfully connect to the room, or fail
        // it if there is an error connecting.
        pendingAction = action
    }

    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        NSLog("provider:performEndCallAction:")

        localMedia?.audioController.stopAudio()
        room?.disconnect()

        action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
        NSLog("provier:performSetMutedCallAction:")
        toggleMic(sender: self)
        action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
        NSLog("provier:performSetHeldCallAction:")

        let cxObserver = callKitCallController.callObserver
        let calls = cxObserver.calls

        guard let call = calls.first(where:{$0.uuid == action.callUUID}) else {
            action.fail()
            return
        }

        if call.isOnHold {
            holdCall(onHold: false)
        } else {
            holdCall(onHold: true)
        }

        action.fulfill()
    }
}



extension ViewController {



     func performStartCallAction(uuid: UUID, roomName: String?) {
        let callHandle = CXHandle(type: .generic, value: roomName ?? "")
        let startCallAction = CXStartCallAction(call: uuid, handle: callHandle)
        let transaction = CXTransaction(action: startCallAction)

        callKitCallController.request(transaction)  { error in
            if let error = error {
                NSLog("StartCallAction transaction request failed: \(error.localizedDescription)")
                return
            }

            NSLog("StartCallAction transaction request successful")

            let callUpdate = CXCallUpdate()
            callUpdate.remoteHandle = callHandle
            callUpdate.supportsDTMF = false
            callUpdate.supportsHolding = true
            callUpdate.supportsGrouping = false
            callUpdate.supportsUngrouping = false
            callUpdate.hasVideo = true

            self.callKitProvider.reportCall(with: uuid, updated: callUpdate)
        }
    }

      func reportIncomingCall(uuid: UUID, roomName: String?, completion: ((NSError?) -> Void)? = nil) {
        let callHandle = CXHandle(type: .generic, value: roomName ?? "")

        print("calling!")
        let callUpdate = CXCallUpdate()
        callUpdate.remoteHandle = callHandle
        callUpdate.supportsDTMF = false
        callUpdate.supportsHolding = true
        callUpdate.supportsGrouping = false
        callUpdate.supportsUngrouping = false
        callUpdate.hasVideo = true

        callKitProvider.reportNewIncomingCall(with: uuid, update: callUpdate) { error in
            if error == nil {
                NSLog("Incoming call successfully reported.")

                // NOTE: CallKit with VoIP push workaround per https://forums.developer.apple.com/message/169511
                self.localMedia?.audioController.configureAudioSession(.videoChatSpeaker)
            } else {
                NSLog("Failed to report incoming call successfully: \(error?.localizedDescription).")
            }

            completion?(error as? NSError)
        }
    }

     func performEndCallAction(uuid: UUID) {
        let endCallAction = CXEndCallAction(call: uuid)
        let transaction = CXTransaction(action: endCallAction)

        callKitCallController.request(transaction) { error in
            if let error = error {
                NSLog("EndCallAction transaction request failed: \(error.localizedDescription).")
                return
            }

            NSLog("EndCallAction transaction request successful")
        }
    }

}

EDIT

As noted in the comments below, it's clear that I wasn't setting the delegate. I have the following init in the vc. When I try to set it in the didFinishLaunchingWithOptons function, it is requiring me to add in argument coder.

ViewController init
required init?(coder aDecoder: NSCoder) {
    let configuration = CXProviderConfiguration(localizedName: "TestApp")
    configuration.maximumCallGroups = 1
    configuration.maximumCallsPerCallGroup = 1
    configuration.supportsVideo = true
    if let callKitIcon = UIImage(named: "iconMask80") {
        configuration.iconTemplateImageData = UIImagePNGRepresentation(callKitIcon)
    }

    callKitProvider = CXProvider(configuration: configuration)
    callKitCallController = CXCallController()

    super.init(coder: aDecoder)

    callKitProvider.setDelegate(self, queue: nil)

}

Appdelegate/didFinishLoadingWithOptions

providerDelegate = ViewController(coder: NSCoder) //this is where its messing up.
gk103
  • 377
  • 5
  • 15
  • 1
    Your delegate is an Optional - do you know something is keeping it alive? Maybe it is going out of scope and therefore destroying. You haven't included code showing what it is set to. – Michael Nov 24 '16 at 03:25
  • Hi Michael, I updated the classes above to include full transparency. I believe its set to CXProviderDelegate. – gk103 Nov 24 '16 at 19:59
  • 2
    In the code you've posted, nothing sets `providerDelegate`, so when `providerDelegate?.performStartCallAction` is called, it will do nothing. Similarly, when you call `viewController.performStartCallAction` there are a couple of code paths where it might not execute. What outcome are you expecting? Have you stepped through the code with the debugger? – Michael Nov 25 '16 at 01:32
  • I apologize for the delay--with Thanksgiving I have had limited access to my computer! To answer your questions-- I have tried to add a protocol providerDelegate{ func reportIncomingCall(uuid: UUID, roomName: String?) } to set the delegate, however when run the delegate is still returning nil & not executing. The thing is, in the ViewController class, if i hook up to a button and run self.reportIncomingCall(uuid: UUID(), roomName: "string"), the function works and will display the callKit incoming call UI. I just need to be able to show up when a voip push is sent. – gk103 Nov 25 '16 at 20:48
  • 1
    @michael as you mentioned the providerDelegate was not being set-- i have added the init() code i have in the ViewController, along with setting providerDelegate, but the issue is that its requiring me to add argument coder: NSCoder which is creating a compiler error. – gk103 Nov 27 '16 at 22:45
  • 1
    I am also having the same issue, please help me out in the same – Manish Gumbal Aug 03 '17 at 11:55
  • Please help. I have implemneted the voip. but, but now 'didReceiveIncomingPushWith' not working but other delegate methods 'pushCredentials' is woking. Please help. – Vineesh TP Jul 07 '18 at 03:04

1 Answers1

2

Try this method if that helps you

My Call Ui Class that will be used in AppDelegate

class CallUI : NSObject {  
    static var shared = CallUI()
    var callControllerClass = CallUIController()

    func initCall(payloadResponse Response: [AnyHashable:Any]) {
        callControllerClass.getDataSortedFromPayload(PayloadResponse: Response)
    }
}

Class Used to handle call Kit Delegates

class CallUIController : UIViewController, CXProviderDelegate {

    var payloadResponse : [AnyHashable:Any]?
    var notificationTypeRx : CallNotificationType?
    var providerName : String?

    let provider = CXProvider(configuration: CXProviderConfiguration(localizedName: SAppName))
    let update = CXCallUpdate()
    var uuidUsed : UUID?
    var providerConfiguration : CXProviderConfiguration?

    func getDataSortedFromPayload(PayloadResponse Response: [AnyHashable:Any]) {
        if let APSData = Response["aps"] as? [String:Any] {
            if let notifyType = APSData["type"] as? String {
                if notifyType == "calling" {
                    self.notificationTypeRx = .StartCall
                    self.showCallUI(ProviderName: nameUsed ?? "")
                }
                else if notifyType == "disconnectCalling" {
                    /// Need to Disconnect Call
                    self.notificationTypeRx = .Endcall
                    /// Dismiss if any Loaded UI
                }
                else{
                    print("Type of notification wasn't found")
                }
            }
        }
        else{
            print("Aps Data was not found")
        }
    }

    func showCallUI(ProviderName Name: String) {

        provider.setDelegate(self, queue: nil)
        uuidUsed = UUID()
        update.hasVideo = true
        update.remoteHandle = CXHandle(type: .phoneNumber, value: Name)
        provider.reportNewIncomingCall(with: uuidUsed!, update: update, completion: { error in })
    }

    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        /// Accept Action
    }

    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        /// Decline Action
    }


    func providerDidReset(_ provider: CXProvider) {
        print("Declined Status")
    }

}

AppDelegate Usage

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
   print("Payload API Response \(payload.dictionaryPayload)")
   if UIApplication.shared.applicationState == .background {
        CallUI.shared.initCall(payloadResponse: payload.dictionaryPayload)
   }
}
iOS Geek
  • 4,825
  • 1
  • 9
  • 30