什么是CallKit?
CallKitiOS 10.0以后出現, 這個開發框架,能夠讓語音或視訊電話的開發者將 UI 界面整合在 iPhone 原生的電話 App 中
將允許開發者將通訊 App 的功能內建在電話 App 的“常用聯絡資訊”,以及“通話記錄”,方便用戶透過原生電話 App,就能直接取用這些第三方功能;允許用戶在通知中心就能直接瀏覽並回覆來電,來電的畫面也將整合在 iOS 原生的 UI 里,總體來說,等於讓 iOS 原本單純用來打電信電話的“電話”功能,能夠結合眾多第三方語音通訊軟件,具備更完整的數碼電話潛力。CallKit 也拓展了在 iOS 8 就出現的 App Extensions 功能,可以讓用戶在接收來電時,在原生電話 App 中就透過第三方 App 辨識騷擾電話(例如詐騙).
iOS 來電提醒:
通過第三方應用檢測並阻止騷擾電話
設置應用以過濾並檢測騷擾電話:
1.前往”設置”>”電話”
2.輕點”來電阻止與身份 識別”
3.允許這些應用阻止來電,並允許其識別主叫號碼。您不會受到被阻止號碼的來電、信息或’FaceTime通話’呼叫下,開啟或關閉該應用。您可以根據優先級對應用進行重新排列。只需輕點”編輯”,然后以您希望的順序來拖移應用。【出現在已阻止的聯系人下的電話號碼都是您手動阻止的號碼】
接到來電后,您的設備將對來電號碼進行檢查,並將其與您安裝的第三方騷然檢測應用的電話號碼列表進行對比。如果匹配,iOS將顯示由該應用選擇的識別標簽,例如“騷擾”或”電話營銷”。如果該應用確定某個電話號碼是騷擾電話,可能會選擇自動阻止來電。來電絕不會發送給第三方開發者。
如果您確定某個電話號碼是騷擾電話,可以在您的設備上手動阻止該號碼。您手動阻止的電話號碼將顯示在黑名單下,如果您不想再使用該應用,可以將它移除。
我想攔截騷擾電話,需要做什么嗎?
1. 前往 App Store 安裝提供騷擾電話識別與攔截的 App;
2. 進入「設置 - 電話 - 來電阻止與身份識別」中開啟第三方 App 的權限;
設置成功后,你的電話、短信、Facetime 都會受到識別和攔截規則的影響。要特別注意的是,正如這項功能的名稱一樣,來電阻止和身份識別實際的作用和效果並不是完全一樣的。
來電阻止和身份識別有什么不同:
- 來電阻止:如果一個號碼被第三方 App 來電阻止,那么你的 iPhone 根本不會響鈴,你也不會在通話記錄中看到有未接電話的提醒,更不會在第三方 App 中看到被攔截的記錄。總而言之,一切就像沒有發生過一樣,你根本沒有辦法知道曾經有一個電話被攔截了。
- 身份識別:當該號碼呼入時,手機依然會按本身的設置響鈴或震動,只是在來電通知的頁面上,在號碼下方會顯示被第三方 App 識別的結果,格式一般為「第三方 App 名稱 + 識別為 + 識別類型」,如「騰訊手機管家識別為:騷擾電話」。在通話記錄中,你也可以看到所有的來電記錄和標記類型。
安裝這些第三方APP會泄漏我的通話記錄嗎?
肯定不會。接下來是細節版的回答。如果你習慣了 Android 手機上的騷擾電話攔截,可能你會對其運作原理有一個大概的了解。一般的作法是,第三方攔截 App 會在本地和雲端同時存在兩個騷擾電話庫,當在網絡允許的情況下,第三方 App 會獲取到手機的來電號碼,並向雲端查詢該號碼是否應被標記為騷擾電話。但在 iOS 上,所有的攔截和識別都只發生在本地,而且不涉及到第三方 App 的參與。
沒錯,第三方 App 並不知道有什么號碼呼入了,當你按上述步驟開啟某一第三方 App 的攔截功能時,在開啟按鈕的那一瞬間,第三方 App 會向系統本地寫入一個騷擾號碼庫,當每次有來電時,系統會將來電號碼與本地的騷擾號碼庫相比較,這個過程第三方 App 既沒有參與,也沒有獲取到你的任何來電信息。
iOS 上的騷擾電話識別率將低於同款產品的 Android 客戶端。
為什么沒有成功識別出騷擾電話?
有三種原因可能導致沒有成功地識別和攔截騷擾電話:
1. 受限於技術實現:也就是上一個問題中剛剛提到的,由到 iOS 采用的是匹配本地數據庫的方式,一個第三方 App 只能寫入數萬條騷擾號碼記錄,這其中肯定存在着漏網之魚。
2. 優先級問題:當你啟用了第三方 App 的攔截功能后,有號碼呼入時,它並不是最高的判斷優先級。當一個號碼呼入時,系統會首先判斷該號碼是否存在於通訊錄,如果它存在,出於人道主義精神,蘋果還是打算讓騙子和他的朋友通話的,這時候第三方 App 的攔截規則不生效。
其次,iOS 本身也會根據郵件、日程等信息,智能地提供電話號碼的呼叫人猜測,當一個號碼被系統智能地識別時,第三方 App 的攔截規則也不會生效。
只有當前兩個判斷條件都沒有命中時,才會與第三方 App 提供的騷擾號碼庫進行比對。
3. 支持機型:由於騷擾電話的攔截和識別是由 iOS 10 新增的 CallKit 提供的,而它只能運行於 64 位的處理器機型上,這意味着只有 iPhone 5s 及以后的機型才能使用攔截功能。
如何使用:
創建一個CallKit工程:
步驟①:創建Call Directory Extension
在對應項目中的file->new->target,選擇Application Extension中的Call Directory Extension,如圖
步驟②:來電阻止和身份識別:
1、項目創建成功后,在項目目錄文件中,生成了Info.plist、CallDirectoryHandler.h、CallDirectoryHandler.m三個文件。我們需要關注的是CallDirectoryHandler.m。
2、打開CallDirectoryHandler.m,里面自動生成了四個方法
- (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context - (BOOL)addBlockingPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context - (BOOL)addIdentificationPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context - (void)requestFailedForExtensionContext:(CXCallDirectoryExtensionContext *)extensionContext withError:(NSError *)error
3.來電識別
- (BOOL)addIdentificationPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context { - CXCallDirectoryPhoneNumber phoneNumbers[] = {+8613533533110,+8613726735411,+86158140043377}; NSArray<NSString *> *labels = @[ @"Dear",@"陳老濕",@"乾坤"]; NSUInteger count = (sizeof(phoneNumbers) / sizeof(CXCallDirectoryPhoneNumber)); for (NSUInteger i = 0; i < count; i += 1) { CXCallDirectoryPhoneNumber phoneNumber = phoneNumbers[i]; NSString *label = labels[i]; [context addIdentificationEntryWithNextSequentialPhoneNumber:phoneNumber label:label]; } return YES; }
注意點:
1、電話號碼前要加區號:+86;
2、電話號碼需要升序排列;
3、號碼不能有重復;
4、手機號碼不能為0;
5、非數字符號不能使用.
4.來電阻止
- (BOOL)addBlockingPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context { CXCallDirectoryPhoneNumber phoneNumbers[] = {13726735412,18005555555 }; NSUInteger count = (sizeof(phoneNumbers) / sizeof(CXCallDirectoryPhoneNumber)); for (NSUInteger index = 0; index < count; index += 1) { CXCallDirectoryPhoneNumber phoneNumber = phoneNumbers[index]; [context addBlockingEntryWithNextSequentialPhoneNumber:phoneNumber]; } return YES; }
注意點:1、電話號碼需要升序排列
CallKit官網:https://developer.apple.com/documentation/callkit
步驟③:編寫來電阻止和身份識別代碼(CallDirectoryHandler):
#import <Foundation/Foundation.h> #import <CallKit/CallKit.h> @interface CallDirectoryHandler : CXCallDirectoryProvider @end
// CallDirectoryHandler.m // CallMark // // Created by HuJinTao on 2018/2/10. // Copyright © 2018年 HuJinTao. All rights reserved. //攔截號碼或者號碼標識的情況下,號碼必須要加國標區號!!!!!!!! #import "CallDirectoryHandler.h" #import "HKNetService.h" #import "CallDefine.h" @interface CallDirectoryHandler () <CXCallDirectoryExtensionContextDelegate> @end @implementation CallDirectoryHandler //開始請求的方法,在打開設置-電話-來電阻止與身份識別開關時,系統自動調用 - (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context { context.delegate = self; NSLog(@"111"); // 獲取權限狀態 //添加黑名單 [self addAllBlockingPhoneNumbersToContext:context]; //添加標識 [self addAllIdentificationPhoneNumbersToContext:context]; [context completeRequestWithCompletionHandler:nil]; } //添加黑名單:根據生產的模板,只需要修改CXCallDirectoryPhoneNumber數組,數組內號碼要按升序排列 - (void)addAllBlockingPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context { NSLog(@"222"); //Sequential相繼的,按次序的 CXCallDirectoryPhoneNumber phoneNumbers[] = { 8613800138000, 8618665710271 }; NSUInteger count = (sizeof(phoneNumbers) / sizeof(CXCallDirectoryPhoneNumber)); for (NSUInteger index = 0; index < count; index += 1) { CXCallDirectoryPhoneNumber phoneNumber = phoneNumbers[index]; [context addBlockingEntryWithNextSequentialPhoneNumber:phoneNumber]; } } - (void)addOrRemoveIncrementalBlockingPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context { CXCallDirectoryPhoneNumber phoneNumbersToAdd[] = { 14085551234 }; NSUInteger countOfPhoneNumbersToAdd = (sizeof(phoneNumbersToAdd) / sizeof(CXCallDirectoryPhoneNumber)); for (NSUInteger index = 0; index < countOfPhoneNumbersToAdd; index += 1) { CXCallDirectoryPhoneNumber phoneNumber = phoneNumbersToAdd[index]; [context addBlockingEntryWithNextSequentialPhoneNumber:phoneNumber]; } CXCallDirectoryPhoneNumber phoneNumbersToRemove[] = { 18005555555 }; NSUInteger countOfPhoneNumbersToRemove = (sizeof(phoneNumbersToRemove) / sizeof(CXCallDirectoryPhoneNumber)); for (NSUInteger index = 0; index < countOfPhoneNumbersToRemove; index += 1) { CXCallDirectoryPhoneNumber phoneNumber = phoneNumbersToRemove[index]; [context removeBlockingEntryWithPhoneNumber:phoneNumber]; } // Record the most-recently loaded set of blocking entries in data store for the next incremental load... } // 添加信息標識:需要修改CXCallDirectoryPhoneNumber數組和對應的標識數組; //CXCallDirectoryPhoneNumber數組存放的號碼和標識數組存放的標識要一一對應, 數組內的號碼要按升序排列 - (void)addAllIdentificationPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context { //獲取Json列表 NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:kCALLGROUPID]; NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"markNum.json"]; NSLog(@"filePath === %@",fileURL); NSData *storedData = [NSData dataWithContentsOfURL:fileURL]; NSMutableArray *phoneNumbers = @[].mutableCopy;//號碼數組 NSMutableArray <NSString *>*labels = @[].mutableCopy;//標記名稱數組 if (storedData) { NSDictionary *dics = [NSJSONSerialization JSONObjectWithData:storedData options:NSJSONReadingAllowFragments error:nil]; NSLog(@"dics === %@",dics); for (NSDictionary *dic in dics) { if (dic[@"phoneNum"] && dic[@"typeName"]) { [phoneNumbers addObject:dic[@"phoneNum"]]; [labels addObject:dic[@"typeName"]]; } } } //關鍵點:addIdentificationEntryWithNextSequentialPhoneNumber NSUInteger count = phoneNumbers.count; NSLog(@"count ==== %ld",count); for (NSUInteger i = 0; i < count; i ++) { CXCallDirectoryPhoneNumber phoneNumber = [phoneNumbers[i] integerValue]; NSLog(@"攔截號碼 %lld ",phoneNumber); NSString *label = labels[i]; NSLog(@"label === %@",label); [context addIdentificationEntryWithNextSequentialPhoneNumber:phoneNumber label:label]; } } - (void)addOrRemoveIncrementalIdentificationPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context { CXCallDirectoryPhoneNumber phoneNumbersToAdd[] = { 14085555678 }; NSArray<NSString *> *labelsToAdd = @[ @"New local business" ]; NSUInteger countOfPhoneNumbersToAdd = (sizeof(phoneNumbersToAdd) / sizeof(CXCallDirectoryPhoneNumber)); for (NSUInteger i = 0; i < countOfPhoneNumbersToAdd; i += 1) { CXCallDirectoryPhoneNumber phoneNumber = phoneNumbersToAdd[i]; NSString *label = labelsToAdd[i]; [context addIdentificationEntryWithNextSequentialPhoneNumber:phoneNumber label:label]; } CXCallDirectoryPhoneNumber phoneNumbersToRemove[] = { 18885555555 }; NSUInteger countOfPhoneNumbersToRemove = (sizeof(phoneNumbersToRemove) / sizeof(CXCallDirectoryPhoneNumber)); for (NSUInteger i = 0; i < countOfPhoneNumbersToRemove; i += 1) { CXCallDirectoryPhoneNumber phoneNumber = phoneNumbersToRemove[i]; [context removeIdentificationEntryWithPhoneNumber:phoneNumber]; } // Record the most-recently loaded set of identification entries in data store for the next incremental load... } #pragma mark - CXCallDirectoryExtensionContextDelegate - (void)requestFailedForExtensionContext:(CXCallDirectoryExtensionContext *)extensionContext withError:(NSError *)error { } @end
注意:如需使用其他主工程下的其他類,必須事此擴展和主工程的兩個Target進行關聯
步驟④:CallKit權限獲取:
@property (nonatomic, assign) int authorityState; //是否有權限 0:未知 1:授權 2:未授權 @property (nonatomic, strong) NSString *fileSize; //文件大小 @property (nonatomic, strong) NSString *fileUrlStr; //zip下載路徑 @property (nonatomic, strong) NSString *fullPath; //zip包存儲的路徑 @property (nonatomic, strong) NSString *destinationPath;//解壓后文件的路徑 @property (nonatomic, strong) NSString *phoneSignTotal; //zip包中包含數據的條數 - (void)getAuthority{ if (@available(iOS 10.0, *)) { CXCallDirectoryManager *manager = [CXCallDirectoryManager sharedInstance]; [manager getEnabledStatusForExtensionWithIdentifier:kCALLMARKID completionHandler:^(CXCallDirectoryEnabledStatus enabledStatus, NSError * _Nullable error) { if (!error) { dispatch_async(dispatch_get_main_queue(), ^{ __block NSString *title = nil; if (enabledStatus == CXCallDirectoryEnabledStatusDisabled) { /* CXCallDirectoryEnabledStatusUnknown = 0, CXCallDirectoryEnabledStatusDisabled = 1, CXCallDirectoryEnabledStatusEnabled = 2, */ title = @"未授權,請在設置->電話授權相關權限"; self.authorityState = 2; _defenceLbl.text = @"來電騷擾防護未開啟"; }else if (enabledStatus == CXCallDirectoryEnabledStatusEnabled) { title = @"授權"; self.authorityState = 1; _defenceLbl.text = @"來電騷擾防護中"; }else if (enabledStatus == CXCallDirectoryEnabledStatusUnknown) { title = @"無法獲取權限信息"; self.authorityState = 0; _defenceLbl.text = @"來電騷擾防護未開啟"; } [self loadData]; }); }else{ UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"權限獲取錯誤" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:defaultAction]; [self presentViewController:alert animated:YES completion:nil]; } }]; } else { } }
步驟⑤:執行立即更新操作:
@property (nonatomic, strong) NSString *fileName; - (void)updateEvent{ if ([UIDevice currentDevice].systemVersion.doubleValue <= 10.0) { UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"本功能暫不支持iOS10.0以下系統,請升級系統后重試" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:defaultAction]; return; } // 獲取號碼庫數據 //獲取zip包的信息 XBWeakSelf //下載zip包 AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.fileUrlStr]]; NSURLSessionDownloadTask *download = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) { NSLog(@"%f",1.0 *downloadProgress.completedUnitCount / downloadProgress.totalUnitCount); } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { _fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename]; XBStrongSelf NSLog(@"targetPath:%@",targetPath); NSLog(@"fullPath:%@",_fullPath); NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject]; self.destinationPath =[cachesPath stringByAppendingPathComponent:@"SSZipArchive"]; self.fileName = [[_fullPath lastPathComponent] substringToIndex:[_fullPath lastPathComponent].length-4]; return [NSURL fileURLWithPath:_fullPath]; } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) { if (error) { [HKMessageBox showAfterDelayText:@"文件下載失敗"]; return ; } if ([SSZipArchive unzipFileAtPath:_fullPath toDestination:_destinationPath overwrite:YES password:nil error:nil delegate:self]) { NSLog(@"解壓完成!"); NSString *txtFileName = [NSString stringWithFormat:@"/%@.txt",self.fileName]; NSData *data = [[NSData alloc]initWithContentsOfFile:[_destinationPath stringByAppendingString:txtFileName]]; NSDictionary *dics = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; //appGroup里的文件路徑 NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:kCALLGROUPID]; NSURL *fileURL = [groupURL URLByAppendingPathComponent:@"markNum.json"]; NSMutableArray *dataArr = @[].mutableCopy; for (NSDictionary *dic in dics) { [dataArr addObject:dic]; } //添加測試號碼 NSDictionary *dic1 = @{@"phoneNum":@"+8618626858645",@"type":@"1",@"typeName":@"測試號碼1"}; NSDictionary *dic2 = @{@"phoneNum":@"+8618868797389",@"type":@"1",@"typeName":@"測試號碼2"}; [dataArr addObject:dic1]; [dataArr addObject:dic2]; //刪除openId 不匹配的元素 NSMutableArray *tempArr = @[].mutableCopy; for (int i = 0; i < dataArr.count; i ++) { if ([dataArr[i][@"type"] integerValue] == 0) { if (![dataArr[i][@"openId"] isEqualToString:[XBIdentify sharedInstance].openId]) { [tempArr addObject:dataArr[i]]; } } } [dataArr removeObjectsInArray:tempArr]; //排序 按照手機號號碼升序排序 for (int i = 0; i < dataArr.count; i ++) { for (int j = i + 1; j < dataArr.count; j ++) { if ([dataArr[i][@"phoneNum"] integerValue] > [dataArr[j][@"phoneNum"] integerValue]) { NSDictionary *temp = dataArr[i]; dataArr[i] = dataArr[j]; dataArr[j] = temp; } } } //重復的保留自己庫的 都不是自己庫留最后一個 NSMutableSet *set = [[NSMutableSet alloc]init]; for (int i = 0; i < dataArr.count; i ++) { for (int j = i + 1; j < dataArr.count; j ++) { if ([dataArr[i][@"phoneNum"] integerValue] == [dataArr[j][@"phoneNum"] integerValue]) { if ([dataArr[i][@"type"] integerValue] == 1) { [set addObject:[NSString stringWithFormat:@"%d",j]]; }else if ([dataArr[j][@"type"] integerValue] == 1){ [set addObject:[NSString stringWithFormat:@"%d",i]]; }else{ [set addObject:[NSString stringWithFormat:@"%d",i]]; } } } } NSLog(@"%@",set); for (NSString *setStr in set) { [dataArr removeObjectAtIndex:[setStr integerValue]]; } NSData *json_data = [NSJSONSerialization dataWithJSONObject:dataArr options:NSJSONWritingPrettyPrinted error:nil]; [json_data writeToURL:fileURL atomically:YES]; dispatch_async(dispatch_get_main_queue(), ^{ [self reloadNumberCage]; }); }else{ [HKMessageBox showAfterDelayText:@"文件解壓失敗"]; NSLog(@"解壓失敗"); } }]; [download resume]; } - (void)reloadNumberCage{ if (@available(iOS 10.0, *)) { CXCallDirectoryManager *manager = [CXCallDirectoryManager sharedInstance]; XBWeakSelf [manager reloadExtensionWithIdentifier:kCALLMARKID completionHandler:^(NSError * _Nullable error) { XBStrongSelf if (error == nil) { [XBUserDefaults setValue:self.fileName forKey:@"numCageDate"]; dispatch_async(dispatch_get_main_queue(), ^{ self.isNewest = YES; [self.tableView reloadData]; }); UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"更新成功" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:defaultAction]; [self presentViewController:alert animated:YES completion:nil]; }else{ NSLog(@"%@",error); UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"更新失敗" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:defaultAction]; [self presentViewController:alert animated:YES completion:nil]; } }]; } else { // Fallback on earlier versions } } - (void)getFilePath { XBWeakSelf NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject]; self.destinationPath =[cachesPath stringByAppendingPathComponent:@"SSZipArchive"]; [[HKNetService sharedInstance] sendGETWithUrl:SERVER_GETNEWESTCAGEZIP params:nil success:^(HKURLResponse *response, HKResultMessage *resultMessage) { XBStrongSelf self.fileSize = response.result[@"fileSize"]; self.fileUrlStr = response.result[@"fileUrl"]; self.phoneSignTotal = response.result[@"phoneSignTotal"]; self.isNewest = [[self.fileUrlStr lastPathComponent] isEqualToString:[NSString stringWithFormat:@"%@.zip",[XBUserDefaults valueForKey:@"numCageDate"]]]; dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; }); } failure:^(HKURLResponse *response, HKResultMessage *resultMessage) { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"下載路徑獲取失敗" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:defaultAction]; }); }]; }
步驟⑥:由於我們獲取數據是在主工程里,而在我們Extension里無法與主工程數據互通,所以要用到App Group
點擊添加,創建一個Group,要注意主工程和Extension中都要給group打上勾
步驟⑦:蘋果還提供了一個更新方法,調用這個方法就相當於重新打開一次打開來電阻止與身份識別里的開關
步驟⑧:
去手機設置->電話->來電阻止與身份識別->開啟我們App的開關權限:
會執行下面這個代碼:
- (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context;方法
如此,便完成此操作~
先在主工程Command+R 運行一次,之后再用CallKitExtension 選擇本工程再次Command + R跑起來,此時開啟權限,則數據才能被錄入,才能實現攔截功能。
注意事項:
1.調試
現在手機上運行App, 再選擇Extension 運行
選擇自己的app - run 然后就可以調試Extension了
2.相關報錯:
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.calldirectorymanager Code=6;CXErrorCodeCallDirectoryManagerErrorExtensionDisabled 權限未打開
號碼庫批量導入案例:注意:必須使用@autoreleasepool{} 否則對象無法釋放,導致無法多次導入
// 添加信息標識:需要修改CXCallDirectoryPhoneNumber數組和對應的標識數組; //CXCallDirectoryPhoneNumber數組存放的號碼和標識數組存放的標識要一一對應, 數組內的號碼要按升序排列 - (void)addAllIdentificationPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context { [self performSelectorOnMainThread:@selector(handlePhoneLibraryWith:) withObject:context waitUntilDone:NO]; } - (void)handlePhoneLibraryWith:(CXCallDirectoryExtensionContext*)context { NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:kCALLGROUPID]; NSUserDefaults *myDefaults = [[NSUserDefaults alloc] initWithSuiteName:kCALLGROUPID]; NSInteger count = [myDefaults integerForKey:@"zipFileCount"]; for (int i=0; i<count; i++){ @autoreleasepool{ NSURL *fileURL = [groupURL URLByAppendingPathComponent:[NSString stringWithFormat:@"%d.json",i]]; NSLog(@"filePath === %@",fileURL); NSError *error = nil; NSData *storedData = [NSData dataWithContentsOfURL:fileURL options:NSDataReadingMappedIfSafe error:&error]; NSMutableArray *phoneNumbers = @[].mutableCopy; NSMutableArray <NSString *>*labels = @[].mutableCopy; if ([error code] == 0) { NSDictionary *dics = [NSJSONSerialization JSONObjectWithData:storedData options:NSJSONReadingAllowFragments error:nil]; for (NSDictionary *dic in dics) { if (dic[@"phoneNum"] && dic[@"typeName"]) { [phoneNumbers addObject:dic[@"phoneNum"]]; [labels addObject:dic[@"typeName"]]; } } dics = nil; storedData = nil; }else { NSLog(@"error===%@",error.userInfo); } NSUInteger count = phoneNumbers.count; NSLog(@"count ==== %ld",count); for (NSUInteger i = 0; i < count; i ++) { CXCallDirectoryPhoneNumber phoneNumber = [phoneNumbers[i] integerValue]; // NSLog(@"攔截號碼 %lld ",phoneNumber); NSString *label = labels[i]; //NSLog(@"label === %@",label); [context addIdentificationEntryWithNextSequentialPhoneNumber:phoneNumber label:label]; } phoneNumbers = nil; labels = nil; } } }
//文件存儲
NSString *path = [[NSBundle mainBundle] pathForResource:@"AllTagNumber" ofType:@"json"]; NSData *JSONData = [NSData dataWithContentsOfFile:path]; BOOL result = [HLFFileManager saveDataByNSFileManagerWithGroups:groupIdentifierExtension FilePath:LocalstorageName value:JSONData]; if (result) { // [[NSUserDefaults standardUserDefaults] setBool:YES forKey:LocalstorageTag]; [[NSUserDefaults standardUserDefaults] setObject:[NSDate dateWithDaysFromNow:-14] forKey:@"CY_LastDownCallDate"]; }else { NSLog(@"寫入失敗"); } + (BOOL)saveDataByNSFileManagerWithGroups:(NSString *)groupName FilePath:(NSString *)FilePath value:(id)value { //獲取共享數據文件夾路徑 NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupName]; //文件路徑 containerURL = [containerURL URLByAppendingPathComponent:FilePath]; //如果該類型支持writeToFile:atomically:函數 if ([NSStringFromClass([value class]) respondsToSelector:@selector(writeToFile:atomically:)]) { //將數據寫入文件 BOOL result = [value writeToURL:containerURL atomically:YES]; return result; } return NO; }