> 日了狗,本篇文章仅供参考吧,大天朝已经不让用callkit了了了了~
> 前提:
iOS版本的微信已经支持了系统层级的打电话体验(6.6版本以上),并且可以直接写入系统的通话记录,点击对应的通话记录又可以直接弹到微信里面直接拨打对应人的语音或者是视频通话。这,其实在体验上已经和系统层级的电话体验差不多了。捣鼓了好几天,算是调通了,总结了一波,同样的,如果你是来寻找代码,那么直接拉到最后!
> 原理:
Callkit:是iOS 10以后才推出的框架,个人理解就是苹果把原本系统层面的电话UI以Api的形式开放出来,让开发者可以集成到自己的项目中,着重强调一下这里的Callkit是系统层级的UI框架,其本身并没有通话功能!我们需要告知这个框架我们的操作打电话,然后通过UI交互,Callkit给我们回调的是用户的操作,比方说End,Start,Answer等,我们需要根据对应的操作在App里面完成通话的逻辑(联想类比一下iPhone和电信提供商的关系,其实真正的通讯是电信提供商去完成的)。
Pushkit:是iOS 8以后推出的一个推送方式,如果你用过静默推送,那么和这个Pushkit非常相似,简单的说,区别于传统的推送方式,Pushkit不会弹出顶部的那个提示条,而是直接进入App里面的回调,再配合上Callkit,那么就能实现类似于系统层级的打电话体验!
通话记录回拨:这个应该算是一个坑吧,后面代码中补充相关逻辑。
> 准备:
公司是用的leanCloud作为推送的三方,如果大家在集成上述两个框架并且实现Voip 类型的通话的时候,若是用的三方推送,请首先用工单的方式提问是否支持Voip类型的推送,我们这边原来使用的是传统的方式导出dev.p12 和 dis.P12(参考下面的文档)。上传之后正常的推送是没有问题的,但是Voip类型的就是发不过来,后来更换了文档中 Token Authentication的方式进行验证,这种方式确实比原来的方式更方便了一点,友情提示,这里面的key文件貌似只能下载一次,请谨慎保存!🤣
> 集成:
PushKit: 满足协议PKPushRegistryDelegate ,然后didFinishLaunchingWithOptions里面去添加如下代码:
let pushRegistry = PKPushRegistry.init(queue: nil) // 这里实测 iOS 10 会比较卡,可以尝试一下在子线程里面去实现初始化,传nil是主线程。
pushRegistry.delegate = self
pushRegistry.desiredPushTypes = [PKPushType.voIP]
按照逻辑来说注册成功后会回调到如下方法中,获取token后,并且传给第三方服务商:
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, forType type: PKPushType) {
var token = ""
for i in 0..<credentials.token.count {
token = token + String(format: "%02.2hhx", arguments: [credentials.token[i]])
}
print("pushRegistry token \(token)")
let temp = AVInstallation.current()
//保存对应的installtion
let insTempString = deleteVoipString(str: temp.objectId ?? "")
let ins = AVInstallation.init(objectId: insTempString + ConstStrting)
ins.apnsTopic = "com.xxxx.xxxx.voip"
ins.setDeviceTokenFrom(credentials.token)
ins.saveInBackground()
print("objectID")
print(ins.objectId ?? "")
}
坑
到此为止,貌似没有任何难度~ 是的那么坑来了! 注意其中ins.apnsTopic = "com.xxxx.xxxx.voip"这一句,如果后台服务商这边是支持voip类型的推送,那么他那边的证书会解析出来正常的bundle id 和 bundle id + .voip的形式!例如 com.baofeng.helpxx那么服务商的证书那边应该解析出 至少包含 com.baofeng.helpxx 和 com.baofeng.helpxx.voip 两种格式。 leanCloud的形式如下:
只有这样才能注册的时候apnsTopic写成 com.baofeng.helper.voip , 那么发送推送的时候才能回调到 pushkit的回调里面! 切记。如果这个有不明白的,欢迎留言!
另外就是我们注意集成的时候需要调节权限如下图:
Callkit:
具体的集成步骤网上是很全面的我也是参考的CallKit iOS 教程 这里面写的非常全面也有对应的原理讲解!英文原文,感谢作者 和 翻译君 ~ 里面的demo可以下载跑起来尝试一下,简单易懂,具体里面的各种代码的逻辑这里就不展开说了。
提示:注意其中的各种回调方法,方法里面的处理逻辑需要各自的业务安排,另外Audio这个类里面的设置按照demo就行,要不可能没有声音。
说一下我们这边的集成原理,我们这边只需要一个唤醒的过程,Callkit就是充当了这个角色,如果App是在前台工作那么我们这边的IM通讯是不需要经过Callkit的。直接拨通对面就好了所以也就是不需要 outCalling的功能。
所以主要的方法就是向Callkit报告电话打入的如下:
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
update.localizedCallerName = handle
update.supportsDTMF = false
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
let call = Call(uuid: uuid, handle: handle)
self.callManager.add(call: call)
}
completion?(error as? NSError)
}
}
tip:callkit接收到reportIncomingCall这个方法传入后选择接听,如果按了HOME键回到主页面,那么出现和系统电话一样的绿色顶部的条文,点击条文会返回App,因为Callkit并不知道什么时候结束,所以需要自己调用结束的方法哦~
坑
注意一下这个代码:
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
update.localizedCallerName = handle
update.supportsDTMF = false
这段代码是在 reportIncomingCall 的时候传入给provider 的update,其中的type是一个枚举包含有3个值 generic、phoneNumber、emailAddress。如果选择了 generic emailAddress 后面是不能在通话记录里面知己弹到App的! 当时我因为通讯录里面保存的名字不是中文,所以选择了generic,导致后面不能从通话记录跳转回App,所以大部分情况这里直接选中phoneNumber,那么如何让通讯录里面的名字正确的显示呢?** update.localizedCallerName** 这里面才是显示的名字,如果这个为空,那么才会出现按照value的值进行显示的情况。(ps:localizedCallerName为空,选中phoneNumber的时候value传入中文iOS 11 会把中文变成拼音🤣,日~)
所以记住,通讯录里面传入的名字是通过.localizedCallerName 来命名的~ 这里也感谢foolishBoy的iOS10适配之 CallKit。
系统通话记录回拨调起App
经过以上的步骤,如果没有特殊情况已经能够实现对应的功能了,现在实现从通话记录点击如下:
点击对应的记录,应该是直接跳转到App里面,那么怎么获取对应的信息呢?
前提是你已经写入了信息,上文的CXCallUpdate里面的信息,现在就通过这种方式回传到了App里面。具体的方法是在 Appdelegate里面,如下:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
guard let handle = userActivity.startCallHandle else {
print("Could not determine start call handle from user activity: \(userActivity)")
return false
}
guard let video = userActivity.video else {
print("Could not determine video from user activity: \(userActivity)")
return false
}
print("handle = \(handle)")
callManager.startCall(handle: handle, videoEnabled: video)
return true
}
这里 startCallHandle、video 是通过一个类扩展来实现的。我直接偷了一个懒,拿demo的StartCallConvertible.swift 和 NSUserActivity+StartCallConvertible.swift 直接用了。
> 总结:
- 经过以上的流程就可以完整的实现从通话开始-通话结束-保存记录-记录回拨的整套流程了。
- Viop类型的推送一定要确定好证书,Apple的证书里面有一个专门的Voip类型的,不过我没有用,这个需要根据具体的需求和三方提供商的需求。
- 注册的Voip类型的推送和原有的推送是两个token,如果需要必须和后台的商量好两种推送的逻辑处理。
- Callkit 本身并不包含即时通讯功能。
>参考:
iOS10适配之 CallKit
官方视频
CallKit iOS 教程
本文代码(这里面使用了leancloud作为推送的三方)
如果有问题或者是疑问,建议等,欢迎留言一起讨论。