从应用程序委托调用时调用工具包 ui 未加载

call kit ui not loading when called from app delegate

我无法启动呼叫工具包 UI 来自应用程序委托的来电。我该怎么做呢?我尝试了扬声器盒示例,但没有帮助。当我 运行 ViewController 中的 reportIncomingCall 方法起作用时。当我在 AppDelegate 中运行 reportIncomingCall 时,它不起作用。我需要它到 Appdelegate 中的 运行,这样我就可以发送 VoIP 通知来报告来电。

这是我的应用委托:

@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
    }

}

并在 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:{[=12=].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")
        }
    }

}

编辑

如以下评论所述,很明显我没有设置委托。我在 vc 中有以下初始化。当我尝试在 didFinishLaunchingWithOptons 函数中设置它时,它要求我添加参数编码器。

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.

如果对您有帮助,试试这个方法

My Call Ui Class 将在 AppDelegate

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

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

Class 用于处理调用 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 用法

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)
   }
}