iOS CallKit与PushKit的集成(二)

声明一下,现在国区的App Store应中国特色社会主义的要求,禁止上架有callkit功能的APP,已有的也要整改,删除callkit功能。

因为篇幅太长,所以把这个分成了三部分,希望读者不要打我。。
上一篇文章讲了PushKit的集成和CallKit打电话,那么这篇就来讲讲如何接电话和挂电话还有其他的一些操作。。
需要继续完善上一篇文章的代码哦。。
iOS CallKit与PushKit的集成(一)

接电话

首先来讲一下如果接电话,来到ProviderDelegate中,添加方法:

func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((Error?) -> Void)?) {
        //准备向系统报告一个 call update 事件,它包含了所有的来电相关的元数据。
        let update = self.callUpdate(handle: handle, hasVideo: hasVideo)
        //调用 CXProvider 的reportIcomingCall(with:update:completion:)方法通知系统有来电。
        provider.reportNewIncomingCall(with: uuid, update: update) { error in
            if error == nil {
                //completion 回调会在系统处理来电时调用。如果没有任何错误,你就创建一个 Call 实例,将它添加到 CallManager 的通话列表。
                let call = Call(uuid: uuid, handle: handle)
                self.callManager.add(call: call)
            }
            //调用 completion,如果它不为空的话。
            completion?(error)
        }
 }

这个方法需要在所有接电话的地方手动调用,需要根据自己的业务逻辑来判断。还有就是不要忘了iOS的版本兼容哦。。

在ProviderDelegate中实现系统接电话的代理:

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        //从 callManager 中获得一个引用,UUID 指定为要接听的动画的 UUID。
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }
        //设置通话要用的 audio session 是 App 的责任。系统会以一个较高的优先级来激活这个 session。
        configureAudioSession()
        //通过调用 answer,你会表明这个通话现在激活
        call.answer()
        //在这里添加自己App接电话的逻辑

        //在处理一个 CXAction 时,重要的一点是,要么你拒绝它(fail),要么满足它(fullfill)。如果处理过程中没有发生错误,你可以调用 fullfill() 表示成功。
        action.fulfill()
    }

回到AppDelegate中,找到之前写的PushKit收到推送的代理方法,在里面添加:

 func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
        guard type == .voIP else {
            log.info("Callkit& pushRegistry didReceiveIncomingPush But Not VoIP")
            return
        }
        log.info("Callkit& pushRegistry didReceiveIncomingPush")
        //别忘了在这里加上你们自己接电话的逻辑,比如连接聊天服务器啥的,不然这个电话打不通的
        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)
        {
            if #available(iOS 10.0, *) {
                ProviderDelegate.shared.reportIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo, completion: { (error) in
                    if let e = error {
                        log.info("CallKit& displayIncomingCall Error \(e)")
                    }
                })
            } else {
                // Fallback on earlier versions
            }
        }
    }

至此,CallKit接电话的逻辑完成了,你只需要在合适的地方调用reportIncomingCall就可以调出系统的通话页面了。

挂电话

来到CallKitManager中,添加方法:

func end(call: Call) {
        //先创建一个 CXEndCallAction。将通话的 UUID 传递给构造函数,以便在后面可以识别通话。
        let endCallAction = CXEndCallAction(call: call.uuid)
        //然后将 action 封装成 CXTransaction,以便发送给系统。
        let transaction = CXTransaction(action: endCallAction)
        requestTransaction(transaction)
}

来到ProviderDelegate中,实现系统代理:

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        //从 callManager 获得一个 call 对象。
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }
        //当 call 即将结束时,停止这次通话的音频处理。
        stopAudio()
        //调用 end() 方法修改本次通话的状态,以允许其他类和新的状态交互。
        call.end()
       //在这里添加自己App挂断电话的逻辑
        //将 action 标记为 fulfill。
        action.fulfill()
        //当你不再需要这个通话时,可以让 callManager 回收它。
        callManager.remove(call: call)
 }

添加完之后,只需要在你自己App挂断电话的地方调用:

if #available(iOS 10.0, *) {
        if let call = CallKitManager.shared.calls.first { //因为我们这里不支持群通话,所以一次只有一个call
               CallKitManager.shared.end(call: call)
        }
}

就可以了。。这里的CallKitManager.shared.calls保存了所有CallKit的通话。是咱们自己写的工具类哦,忘了的话自己翻翻上篇文章。

至此,CallKit挂电话的逻辑结束。。

通话暂时挂起

来到CallKitManager中,添加方法:

func setHeld(call: Call, onHold: Bool) {
        //这个 CXSetHeldCallAction 包含了通话的 UUID 以及保持状态
        let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: onHold)
        let transaction = CXTransaction()
        transaction.addAction(setHeldCallAction)
        
        requestTransaction(transaction)
}

来到ProviderDelegate中,实现系统代理:

func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }
        //获得 CXCall 对象之后,我们要根据 action 的 isOnHold 属性来设置它的 state。
        call.state = action.isOnHold ? .held:.active
        //根据状态的不同,分别进行启动或停止音频会话。
        if call.state == .held {
            stopAudio()
        } else {
            startAudio()
        }
        //在这里添加你们自己的通话挂起逻辑
        action.fulfill()
}

添加完之后,只需要在你自己App通话暂时挂起的地方调用:

if #available(iOS 10.0, *) {
        if let call = CallKitManager.shared.calls.first { 
               CallKitManager.shared.setHeld(call: call, onHold: true)
        }
}

就可以了。。

至此,CallKit通话暂时挂起的逻辑结束。。

麦克风静音

来到CallKitManager中,添加方法:

func setMute(call: Call, muted: Bool) {
        //CXSetMutedCallAction设置麦克风静音
        let setMuteCallAction = CXSetMutedCallAction(call: call.uuid, muted: muted)
        let transaction = CXTransaction()
        transaction.addAction(setMuteCallAction)
        requestTransaction(transaction)
}

来到ProviderDelegate中,实现系统代理:

func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
        guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }
        //获得 CXCall 对象之后,我们要根据 action 的 ismuted 属性来设置它的 state。
        call.state = action.isMuted ? .muted : .active
        //在这里添加你们自己的麦克风静音逻辑
        action.fulfill()
    }

添加完之后,只需要在你自己App麦克风静音的地方调用:

if #available(iOS 10.0, *) {
        if let call = CallKitManager.shared.calls.first { 
               CallKitManager.shared.setMute(call: call, muted: true)
        }
}

就可以了。。

至此,CallKit麦克风静音的逻辑结束。。

到这里,在App内互动的CallKit的基本功能都已经集成完毕,其实到后面大家就能看出来,文章中所有的功能实现,都是先在CallKitManager写用户需要调用的方法,在ProviderDelegate里面实现系统的代理方法,并且加上自己的通话逻辑。

关于系统扬声器与听筒的切换

这里不讲如何切换扬声器与听筒,只讲如何监听切换,保持App内通话页面免提的状态跟系统通话页面的一致。
在自己的通话页面上添加通知监听:

NotificationCenter.default.addObserver(forName: NSNotification.Name.AVAudioSessionRouteChange, object: nil, queue: OperationQueue.main) {[weak self] (noti) in
            guard let w = self else { return }
            if #available(iOS 10.0, *) {
                let route = AVAudioSession.sharedInstance().currentRoute
                for desc in route.outputs {
                    if desc.portType == "Speaker" {
                        // "免提功能已开启"
                    } else {
                        // "对方已接通,请使用听筒接听"
                    }
                }
            }
}

至此,CallKit的主要功能集成完毕,下一篇文章将继续讲解如何从系统通话记录中直接拨打App的电话。
iOS CallKit与PushKit的集成(完结篇)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 声明一下,现在国区的App Store应中国特色社会主义的要求,禁止上架有callkit功能的APP,已有的也要整...
    捡书阅读 3,613评论 3 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 如果我们不去提升和扩展意识,生活中的问题始终得不到真正的解决。 什么是提升意识? 你一直抱怨老公爱喝酒,或者报怨他...
    兰雪梅阅读 1,114评论 0 0
  • 我小时候,过年是一年中最开心的日子,爸妈会带我从城里回到在乡下的奶奶家。奶奶家有大我两个月的哥哥,在那个北方的小山...
    兰呆呆和兰萌萌阅读 989评论 6 14
  • 白云蓝天青草地, 阳光斜照着,暖暖的。 场上五彩缤纷, 红黄的球衣与绿草拼凑成美丽的画卷。 白色的界线与龙门成了画...
    3fd25a0a9f73阅读 475评论 0 0