推薦序
本文介紹了 iOS 10 中的 Call Directory Extension 特性,並且最終 Demo 出一個來電黑名單的 App。
作者:余龍澤,哈工大軟件工程大四學生,之前在美圖公司實習,在iOS學習道路上不斷努力中。
感謝作者授權,以下是正文。
iOS 10 中引入了許多令人振奮的新特性,其中 CallKit 讓我特別感興趣。這是一個非常重要的 API,繼 2014 年蘋果推出 VoIP 證書后,這次 VoIP 接口的開放,以及一個全新的 App Extension,簡直是 VOIP 的福音,可見蘋果對 VOIP 的重視。並且,”that enable call blocking and caller identification. You can create an app extension that can associate a phone number with a name or tell the system when a number should be blocked.” 這意味着現在可以通過 Call Directory Extension 來實現電話黑名單功能了。Cool~ 本文簡單闡述了如果實現簡單的來電黑名單功能。
閱讀須知:目前學習的資料也僅限相關 API,另外 API 也沒有詳細的注釋,所以本文主要是個人探索所得,如果有什么錯誤,還望見諒並予以指正。現在,讓我們開始吧~
API 介紹
Extension 一直給我的印象就是很輕量,單一的,就如之前接觸的 Photo Editing Extension 一樣,使用起來十分簡單。這次的 Call Directory Extension 也不出例外,出奇的簡單。只涉及了兩個類,四個方法。下面我們逐一介紹:
//// CXCallDirectoryProvider.h// CallKit@available(iOS 10.0, *) public class CXCallDirectoryProvider : NSObject, NSExtensionRequestHandling { public func beginRequest(with context: CXCallDirectoryExtensionContext)}
首先是第一個類 CXCallDirectoryProvider,它是來電的響應者,為我們提供了 beginRequest 方法,該方法在 Containing App 調用 reload 或者在 設置 —> 電話 —> Call Blocking & Identification 里開啟權限的時候,會自動被調用。所以我們之后將要重寫它,來實現黑名單相關邏輯。怎么樣,簡單吧~
Now, Go on~
接下來是另外一個類 CXCallDirectoryExtensionContext,它提供了另外三個方法,如下所示:
//// CXCallDirectoryExtensionContext.h// CallKit@available(iOS 10.0, *) public class CXCallDirectoryExtensionContext : NSExtensionContext { public func addBlockingEntry(withNextSequentialPhoneNumber phoneNumber: String) public func addIdentificationEntry(withNextSequentialPhoneNumber phoneNumber: String, label: String) public func completeRequest(completionHandler completion: ((Bool) -> Swift.Void)? = nil)}
不難看出,CXCallDirectoryExtensionContext 主要負責提交我們處理好的請求。說白點,我們利用它來讓系統知道,我們對某個來電所做出的判斷。 addBlockingEntry 方法,接受一個電話號碼字符串,形如 “+8618…69” (PS:不要問我為什么要加區號 .. 這都是血與淚的經驗),來直接加入黑名單,也就是不接聽該來電。addIdentificationEntry 方法,接受一個電話號碼字符串以及對該號碼的描述,也就是來電的時候需要顯示的內容。 completeRequest 也就是提交之前的處理結果。至此,我們所要做的工作就完成了。
實戰演示
雖然自認為上面的描述已經夠詳細了,不過這里還是有必要詳細走一遍流程,以免遺漏。
開發環境:Xcode8.0 Beta + 64 位 iOS10 設備(至於為什么 64 位,之后再解釋,說多了都是淚 ..)
1. 創建工程
沒什么特別。 Xcode —> File —> New —> Project。隨便選個 iOS Application,創建即可。這里我選擇開發語言為 Swift,你隨意~。
這里我們的目標是來電黑名單,也就是 Extension 部分,所以創建好的 Containing App,不用做什么改動。
2. 添加 Extension
Xcode —> File —> New —> Target。創建一個 Call Directory Extension,如下圖所示:
這里注意下底部的說明, (This extension and the app it is bundled with must be 64-bit only)也就是,這個 extension 只支持 64 位的設備,坑爹有沒有!!之前創建太急,沒認真看,用那台 5C 倒騰了半天,就是出問題。只好狠心把主力機也升級了。
創建好 Extension,會彈出這樣的提示框:
詢問我們是否激活這個 scheme,當然選擇激活咯,繼續~
之后只要關注 xxxHandler.swift 即可,xxx 是你之前創建的 extension 命名。
這里的相關代碼如下:
import Foundation import CallKit class CallDirectoryHandler: CXCallDirectoryProvider { override func beginRequest(with context: CXCallDirectoryExtensionContext) { // --- 1 guard let phoneNumbersToBlock = retrievePhoneNumbersToBlock() else { NSLog("Unable to retrieve phone numbers to block") let error = NSError(domain: "CallDirectoryHandler", code: 1, userInfo: nil) context.cancelRequest(withError: error) return } // --- 2 for phoneNumber in phoneNumbersToBlock { context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber) } // --- 3 guard let (phoneNumbersToIdentify, phoneNumberIdentificationLabels) = retrievePhoneNumbersToIdentifyAndLabels() else { NSLog("Unable to retrieve phone numbers to identify and their labels") let error = NSError(domain: "CallDirectoryHandler", code: 2, userInfo: nil) context.cancelRequest(withError: error) return } // --- 4 for (phoneNumber, label) in zip(phoneNumbersToIdentify, phoneNumberIdentificationLabels) { context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label) } // --- 5 context.completeRequest() } private func retrievePhoneNumbersToBlock() -> [String]? { // retrieve list of phone numbers to block return ["+8618xxxx157"] } private func retrievePhoneNumbersToIdentifyAndLabels() -> (phoneNumbers: [String], labels: [String])? { // retrieve list of phone numbers to identify, and their labels return (["+8618xxxx569"], [" 測試 "]) } }
一個簡單的來電黑名單,我們只要補全 retrievePhoneNumbersToBlock
和 retrievePhoneNumbersToIdentifyAndLabels
中的相關數據即可,它們分別表示直接加入黑名單的號碼以及識別出來,需要判斷的號碼。
現在我們具體看一下這個類到底做了什么。
beginRequest
,該方法在 Containing App 調用 reload 或者在 設置 —> 電話 —> Call Blocking & Identification 里開啟權限的時候,會自動被調用。每次調用,都會提交當前的黑名單列表,具體操作如下:
在 // —- 1 中,先判斷是否成功調用了 retrievePhoneNumbersToBlock
方法,如果沒有,則打印 Log: Unable to retrieve phone numbers to block,然后直接終止這次請求並返回。
在 // —- 2 中,遍歷添加黑名單中的號碼,這里的號碼將直接攔截。
在 // —- 3 中,先判斷是否成功調用了 retrievePhoneNumbersToIdentifyAndLabels
方法,如果沒有,則打印 Log: Unable to retrieve phone numbers to identify and their labels,然后直接終止這次請求並返回。
在 // —- 4 中,遍歷添加識別后的號碼及其描述,這里的號碼將連帶描述一起顯示。
在 // —- 5 中,完成提交請求。
到這里,代碼已經全部完成了。
3. 開啟權限
之后我們運行該 App 到設備中,然后進入設備的設置 —> 電話 —> Call Blocking & Identification,開啟我們的 App 即可。如下圖所示:
:
相關思考及后續
雖然實現黑名單功能很簡單,但是這里我認為主要的問題應該是集中在,如何編輯這個黑名單列表。列表數據項可能很多,並且數據可能是實時更新添加的,那應該怎么做才更好呢?這里我的第一反應就是利用 App Group 實現數據共享,在 Containing App 完成相關的數據操作,在 Extension App 中去獲取即可。至於可行性,倒是沒有驗證過,如果不行,就當我瞎比比咯~。 當然,可能還有其他的辦法,以及可能還會遇到其他的問題,這里在之后的學習過程中,我會逐步完善。
當然,對於 CallKit 的學習,我也僅限於這一兩天,還是沒有資料的情況下。所以文中難免存在各種錯誤以及遺漏,歡迎指正。
這之后,繼續 CallKit 的學習,實現它的另外一個功能:VoIP App。 wait…
Enjoy it~
參考鏈接
Enhancing VoIP Apps with CallKit:https://developer.apple.com/videos/play/wwdc2016/230/