请稍侯

CallKit使用实现总结

14 January 2017

CallKit使用实现总结

CallKit不仅让VoIP应用具有系统电话一样的功能,还能帮助系统实现来电识别等功能;但本身并不具备voip功能。

  • 引入库PushKit.framework、CallKit.framework;

  • AppDelegate.h添加

#import <PushKit/PushKit.h>

@class ProviderDelegate;
@class SpeakerboxCallManager;

@property (nonatomic, strong) ProviderDelegate *providerDelegate NS_AVAILABLE_IOS(10_0);
@property (nonatomic, strong) SpeakerboxCallManager *callManager NS_AVAILABLE_IOS(10_0);
@property (nonatomic, strong) PKPushRegistry* pushRegistry;


  • AppDelegate.m添加
#import "AppName-Swift.h"

//在didFinishLaunchingWithOptions方法里添加如下代码
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
   //初始化pushRegistry
   self.pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
   self.pushRegistry.delegate = self;
   self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
   self.callManager = [SpeakerboxCallManager new];
   self.providerDelegate = [[ProviderDelegate alloc] initWithCallManager:self.callManager];
}


  • 创建AppDelegateExtension.swift, 让AppDelegate实现PKPushRegistryDelegate协议,如下:
import UIKit
import PushKit


extension AppDelegate : PKPushRegistryDelegate{
    
    // MARK: PKPushRegistryDelegate Implementation
    
    public func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, forType type: PKPushType) {
        /*
         Store push credentials on server for the active user.
         For sample app purposes, do nothing since everything is being done locally.
         */
    }
    
    public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) {
        guard type == .voIP else { return }
        
        if let uuidString = payload.dictionaryPayload["UUID"] as? String,
            let handle = payload.dictionaryPayload["handle"] as? String,
            let hasVideo = payload.dictionaryPayload["hasVideo"] as? Bool,
            let uuid = UUID(uuidString: uuidString)
        {
            displayIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo)
        }
    }
    
    /// Display the incoming call to the user
    func displayIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
        if #available(iOS 10.0, *) {
            self.providerDelegate?.reportIncomingCall(uuid: uuid, handleValue: handle, hasVideo: hasVideo, completion: completion)
        } else {
            // Fallback on earlier versions
        }
        
    }

    
}

  • AppName-Bridging-Header.h文件里添加
#import "AppDelegate.h"

  • ProviderDelegate.swift实现
import UIKit
import CallKit
import AVFoundation

let NotificationName_CallStart = Notification.Name("callStart")
let NotificationName_CallEnd = Notification.Name("callEnd")

@available(iOS 10.0, *)
class ProviderDelegate: NSObject,CXProviderDelegate {
    private let provider:CXProvider
    
    private let callController = CXCallController()
    /// The app's provider configuration, representing its CallKit capabilities
    static var providerConfiguration: CXProviderConfiguration {
        let localizedName = (Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String) ?? "CallKit示例"
        let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)
        providerConfiguration.supportsVideo = true
        
        providerConfiguration.maximumCallsPerCallGroup = 1
        
        providerConfiguration.supportedHandleTypes = [.generic,.phoneNumber,.emailAddress]
        
        if let iconMaskImage = UIImage(named: "IconMask") {
            providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation(iconMaskImage)
        }
        
        providerConfiguration.ringtoneSound = "my_calling.caf"
        

        return providerConfiguration
    }
    
    override init() {
        provider = CXProvider(configuration: type(of: self).providerConfiguration)
        super.init()
        provider.setDelegate(self, queue: nil)
    }
    //MARK: - Call
    
    func call(handleValue:String) {
        let startCallAction = CXStartCallAction(call: UUID(),handle: CXHandle(type: .phoneNumber, value: handleValue))
        
        let transaction = CXTransaction()
        transaction.addAction(startCallAction)
        
        callController.request(transaction) { (err: Error?) in
            print(err ?? "CallController Request Error")
        }
    }
    //MARK: - Ending Call
    func endCall(uuids : [UUID],completion: @escaping (UUID) -> Void) {
        let uuid = uuids.first

        let action = CXEndCallAction(call: uuid!)
        let trans = CXTransaction()
        trans.addAction(action)
        callController.request(trans, completion: { (err) in
            print(err ?? "CallController Request Error")
            completion(action.uuid)
        })

    }


    // MARK: Incoming Calls
    
    /// Use CXProvider to report the incoming call to the system
    func reportIncomingCall(uuid: UUID, handleValue: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
        // Construct a CXCallUpdate describing the incoming call, including the caller.
        let update = CXCallUpdate()
        update.remoteHandle = CXHandle(type: .phoneNumber, value: handleValue)
        update.hasVideo = hasVideo
        
        // Report the incoming call to the system
        provider.reportNewIncomingCall(with: uuid, update: update) { error in
            /*
             Only add incoming call to the app's list of calls if the call was allowed (i.e. there was no error)
             since calls may be "denied" for various legitimate reasons. See CXErrorCodeIncomingCallError.
             */
            if error == nil {
                print("calling")
                
            }
        }
    }
    
    func providerDidReset(_ provider: CXProvider) {
        print("Provider did reset")
        

    }
    
    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
        let update = CXCallUpdate()
        update.remoteHandle = action.handle
        provider.reportOutgoingCall(with: action.uuid, startedConnectingAt: Date())
        NotificationCenter.default.post(name: NotificationName_CallStart, object: self, userInfo: ["uuid":action.uuid])
        action.fulfill(withDateStarted: Date())
        
    }
    
    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        NotificationCenter.default.post(name: NotificationName_CallStart, object: self, userInfo: ["uuid":action.uuid])
        action.fulfill(withDateConnected: Date())
        
    }
    
    
    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        action.fulfill()
        NotificationCenter.default.post(name: NotificationName_CallEnd, object: self, userInfo: ["uuid":action.uuid.uuidString])
    }
    
    func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
        action.fulfill()
    }
    
    func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
        action.fulfill()
        print("Timed out \(#function)")
        
        // React to the action timeout if necessary, such as showing an error UI.
    }
    
    
    
    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        print("Received \(#function)")
        
    }
    
    
    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        print("Received \(#function)")
        
        /*
         Restart any non-call related audio now that the app's audio session has been
         de-activated after having its priority restored to normal.
         */
    }

}

其他的实现参考:Apple官方示例

参考文章:

CallKit的使用介绍:http://www.jianshu.com/p/2bf4f186dfd9

iOS10新特性之CallKit开发VoIP详解:http://www.jianshu.com/p/35b59089134e

iOS Call Kit for VOIP:http://www.jianshu.com/p/3bf73a293535

iOS10适配之 CallKit:http://www.jianshu.com/p/305bd923c1ae

苹果官方文档:https://developer.apple.com/reference/callkit