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