iOS开发|Swift Callkit扩展--来电标记

需求:需要实现项目中根据号码从服务器查询返回的标记写入calllkit放骚扰标记系统库中,来电时显示该标记
最终的效果如图所示:
iOS开发|Swift Callkit扩展--来电标记
文章图片

一、准备工作
1、新建一个swift 项目
2、创建callkit扩展:选择file->new->target,选择Call Directory Extension,输入扩展的名称,创建好后选择Activate

iOS开发|Swift Callkit扩展--来电标记
文章图片

iOS开发|Swift Callkit扩展--来电标记
文章图片

iOS开发|Swift Callkit扩展--来电标记
文章图片

3、想实现项目与扩展的数据共享,需要打开Capabilities-->App Groups的开关,并点击“+”新建一个app groups(一般命名是以group. 开头)
iOS开发|Swift Callkit扩展--来电标记
文章图片

iOS开发|Swift Callkit扩展--来电标记
文章图片

iOS开发|Swift Callkit扩展--来电标记
文章图片

4、同样的,项目中也需要打开Capabilities-->App Groups的开关,选择3中创建好的app groups
5、运行程序,需要在设置-->电话-->来电阻止与身份识别-->找到项目打开对应的开关
iOS开发|Swift Callkit扩展--来电标记
文章图片

iOS开发|Swift Callkit扩展--来电标记
文章图片

iOS开发|Swift Callkit扩展--来电标记
文章图片

准备工作做好了,下面代码逻辑的展示
1、在主界面创建3个按钮,分别为 检查权限、写入共享数据、读取共享数据
1)创建扩展Manager、文件Manager、存储数据的dic、以及app group的url,并初始化

var exManager : CXCallDirectoryManager!var fileManager : FileManager!var dic: NSMutableDictionary!var containURL : URL! override func viewDidLoad() { super.viewDidLoad() dic = NSMutableDictionary.init() exManager = CXCallDirectoryManager.sharedInstance fileManager = FileManager.defaultdic.setValue("中介", forKey: "8613120076711") dic.setValue("骗子", forKey: "8612345678901")}


2)检查权限:目的是检查callkit标记功能的权限是否开启(开启方法:见上面第5条),若未开启,数据是无法写入系统标记库的,需要用户手动开启
exManager.getEnabledStatusForExtension(withIdentifier: CALLKITEX_IDENTIFIER) { (status : CXCallDirectoryManager.EnabledStatus, error) in if error != nil{ self.promissiondDesLabel.text = "权限获取发生错误"+error.debugDescription }else{ switch status { case .disabled: self.promissiondDesLabel.text = "权限未开启" break case .enabled: self.promissiondDesLabel.text = "权限已开启" break default: self.promissiondDesLabel.text = "权限未知" break } } }


3)写入共享数据:在app group中创建共享文件,并将数据存储到文件里,然后将数据录入系统
self.containURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: APPGROUP_IDENTIFIER) self.containURL = containURL.appendingPathComponent(FILE_NAME) let filepath = self.containURL.path let jsonStr = NSMutableString.init() jsonStr.append("[") for (number,identifier) in dic { let number = number as! String let identifier = identifier as! String let dicStr = String.init(format: "{\"%@\":\"%@\"},\n", number,identifier) jsonStr.append(dicStr) } jsonStr.append("]")print("jsonstr \(jsonStr)")do{ try jsonStr.write(toFile: filepath, atomically: true, encoding: String.Encoding.utf8.rawValue) }catch let error { print("写入文件出错 \(error)") }//将数据录入系统 if self.promissiondDesLabel.text == "权限已开启" { exManager.reloadExtension(withIdentifier: CALLKITEX_IDENTIFIER) { (error) in if error != nil{ print("写入系统出错 \(error)") }else{ print("写入系统成功") } } }


注:写入报错主要有以下几个原因:
(1)Error Domain=com.apple.CallKit.error.calldirectorymanager Code=1 ;CXErrorCodeCallDirectoryManagerErrorNoExtensionFound 该错误可能出现的原因是identifier设置的不对 注意不要使用app groups 使用的是Call Directory Extension 的identifier
(2)Error Domain=com.apple.CallKit.error.calldirectorymanager Code=2 ;CXErrorCodeCallDirectoryManagerErrorLoadingInterrupted加载时被中断有可能是因为addAllIdentificationPhoneNumbersToContext中数据处理出错,打断点调试一下
(3)Error Domain=com.apple.CallKit.error.calldirectorymanager Code=3;CXErrorCodeCallDirectoryManagerErrorEntriesOutOfOrder可能是因为加载数据格式错误比如号码中带有符号,号码没有增序排列
(4)Error Domain=com.apple.CallKit.error.calldirectorymanager Code=4;CXErrorCodeCallDirectoryManagerErrorDuplicateEntries可能是的数据有重复
(5)Error Domain=com.apple.CallKit.error.calldirectorymanagerCode=6;CXErrorCodeCallDirectoryManagerErrorExtensionDisabled 权限未打开
3)读取共性数据:目的在于查看数据是否已经写入系统,并且可读取
self.containURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: APPGROUP_IDENTIFIER) self.containURL = containURL?.appendingPathComponent(FILE_NAME) //打开文件,如果file为nil则说明文件不存在 let file = fopen((self.containURL?.path as NSString?)!.utf8String, "r") iffile==nil { print("共享文件不存在_说明尚未写入数据") }else{ print("共享文件存在") var str:String! var jsonData : Data! var array : NSMutableArray = NSMutableArray.init() var dic : NSMutableDictionary = NSMutableDictionary.init() do { str = try String.init(contentsOf: containURL!, encoding: String.Encoding.utf8) jsonData = https://www.it610.com/article/str.data(using: String.Encoding.utf8)! let originalArray = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) if (originalArray as AnyObject).isKind(of: NSArray.classForCoder()){ print("解析类型是数组") array.addObjects(from: originalArray as! [Any]) if array.count == 0 { return }//2、利用数组去重 var temp= [NSDictionary]() var idxArr = [String]() for dic in array{ let dic = dic as! NSDictionary let number = dic.allKeys[0] as! String let identifier = dic.allValues[0] as! String if !idxArr.contains(number){ idxArr.append(number) temp.append(dic) } } print("解析类型是数组 temp\(array) \(temp)") }else{ print("解析类型是字典") for (number, identifier) in (originalArray as! NSDictionary){ dic.setValue(identifier, forKey: number as! String) } print("解析类型是字典 temp\(originalArray) \(dic)") }}catch let error { print("共享内存读取失败 \(error)") } }


2、查看创建好的扩展,里面有个CallDirectoryHandler.swift的文件,号码写入系统标记库的逻辑就是在这个类里面执行
注:ios11中callkit扩展新增了两个方法,由于需要兼容10及以上的版本,所以将多出来的以及没用到方法注释掉了
目的:将共享目录中对应文件的数据读取出来,添加到系统让骚扰标记库中
号码的格式需要注意:1)拦截号码或者号码标识的情况下,号码必须要加国标区号!,
2)数组内号码要按升序排列
里面主要用到了两个方法
1)beginRequest //开始请求的方法,在打开设置-电话-来电阻止与身份识别开关时,系统自动调用
2)addAllIdentificationPhoneNumbers //添加标记号码:根据生产的模板,只需要修改CXCallDirectoryPhoneNumber数组,数组内号码要按升序排列
具体代码如下:
【iOS开发|Swift Callkit扩展--来电标记】
//拦截号码或者号码标识的情况下,号码必须要加国标区号!!!!!!!! import Foundation import CallKit class CallDirectoryHandler: CXCallDirectoryProvider { //开始请求的方法,在打开设置-电话-来电阻止与身份识别开关时,系统自动调用 override func beginRequest(with context: CXCallDirectoryExtensionContext) { context.delegate = self addAllIdentificationPhoneNumbers(to: context) context.completeRequest() } //添加标记号码 private func addAllIdentificationPhoneNumbers(to context: CXCallDirectoryExtensionContext) {var containerURL : URL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.group.com.callkit")! containerURL = containerURL.appendingPathComponent("data") //打开文件,如果file为nil则说明文件不存在 let file = fopen((containerURL.path as NSString?)!.utf8String, "r") iffile == nil { return } //containerURL = containerURL?.appendingPathComponent("Library/Caches/callkit.json") var str:String! var jsonData : Data! var array : NSArray! do { str = try String.init(contentsOf: containerURL, encoding: String.Encoding.utf8) jsonData = https://www.it610.com/article/str.data(using: String.Encoding.utf8)! array = (try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSArray)! if array.count == 0 { return } print("string extension "+str+" \(jsonData) \(array) ") for dic in array { let dic = dic as! NSDictionary let number = dic.allKeys[0] let identifier = dic.allValues[0] print("string extension\(number) \(identifier) ") let phoneNum = CXCallDirectoryPhoneNumber(truncating: NSNumber.init(value: ((number as? NSString)?.integerValue)!)) context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNum, label: identifier as! String) } }catch let error { print("string extension error \(error)") } } } extension CallDirectoryHandler: CXCallDirectoryExtensionContextDelegate { func requestFailed(for extensionContext: CXCallDirectoryExtensionContext, withError error: Error) { // An error occurred while adding blocking or identification entries, check the NSError for details. // For Call Directory error codes, see the CXErrorCodeCallDirectoryManagerError enum in . // // This may be used to store the error details in a location accessible by the extension's containing app, so that the // app may be notified about errors which occured while loading data even if the request to load data was initiated by // the user in Settings instead of via the app itself. } }


demo链接:demo下载
如果上面的链接打不开-->github下载

    推荐阅读