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閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 聲明一下,現在國區的App Store應中國特色社會主義的要求,禁止上架有callkit功能的APP,已有的也要整...
    撿書閱讀 3,838評論 3 9
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,830評論 18 139
  • 如果我們不去提升和擴展意識,生活中的問題始終得不到真正的解決。 什么是提升意識? 你一直抱怨老公愛喝酒,或者報怨他...
    蘭雪梅閱讀 1,188評論 0 0
  • 我小時候,過年是一年中最開心的日子,爸媽會帶我從城里回到在鄉下的奶奶家。奶奶家有大我兩個月的哥哥,在那個北方的小山...
    蘭呆呆和蘭萌萌閱讀 1,004評論 6 14
  • 白云藍天青草地, 陽光斜照著,暖暖的。 場上五彩繽紛, 紅黃的球衣與綠草拼湊成美麗的畫卷。 白色的界線與龍門成了畫...
    3fd25a0a9f73閱讀 499評論 0 0