CallKit详解(来电提醒+骚扰拦截)

什么是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 的权限;

CallKit详解(来电提醒+骚扰拦截)_第1张图片

 

设置成功后,你的电话、短信、Facetime 都会受到识别和拦截规则的影响。要特别注意的是,正如这项功能的名称一样,来电阻止和身份识别实际的作用和效果并不是完全一样的。

来电阻止和身份识别有什么不同

  • 来电阻止:如果一个号码被第三方 App 来电阻止,那么你的 iPhone 根本不会响铃,你也不会在通话记录中看到有未接电话的提醒,更不会在第三方 App 中看到被拦截的记录。总而言之,一切就像没有发生过一样,你根本没有办法知道曾经有一个电话被拦截了。
  • 身份识别:当该号码呼入时,手机依然会按本身的设置响铃或震动,只是在来电通知的页面上,在号码下方会显示被第三方 App 识别的结果,格式一般为「第三方 App 名称 + 识别为 + 识别类型」,如「腾讯手机管家识别为:骚扰电话」。在通话记录中,你也可以看到所有的来电记录和标记类型

CallKit详解(来电提醒+骚扰拦截)_第2张图片

安装这些第三方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,如图

CallKit详解(来电提醒+骚扰拦截)_第3张图片

步骤②:来电阻止和身份识别:

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 *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 
#import 
@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 () 
@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 *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 *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详解(来电提醒+骚扰拦截)_第4张图片

步骤④: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

CallKit详解(来电提醒+骚扰拦截)_第5张图片

 

点击添加,创建一个Group,要注意主工程和Extension中都要给group打上勾

 

步骤⑦:苹果还提供了一个更新方法,调用这个方法就相当于重新打开一次打开来电阻止与身份识别里的开关

CallKit详解(来电提醒+骚扰拦截)_第6张图片

 

步骤⑧

去手机设置->电话->来电阻止与身份识别->开启我们App的开关权限:

会执行下面这个代码:

- (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context;方法

 

如此,便完成此操作~

 先在主工程Command+R 运行一次,之后再用CallKitExtension 选择本工程再次Command + R跑起来,此时开启权限,则数据才能被录入,才能实现拦截功能。

 

注意事项

1.调试

现在手机上运行App, 再选择Extension 运行

CallKit详解(来电提醒+骚扰拦截)_第7张图片

选择自己的app - run 然后就可以调试Extension了

CallKit详解(来电提醒+骚扰拦截)_第8张图片

2.相关报错:

1) 

Error Domain=com.apple.CallKit.error.calldirectorymanager Code=1CXErrorCodeCallDirectoryManagerErrorNoExtensionFound 该错误可能出现的原因是identifier   设置的不对 注意不要使用app groups 使用的是Call Directory Extension 的identifier

 

2)

Error Domain=com.apple.CallKit.error.calldirectorymanager Code=2CXErrorCodeCallDirectoryManagerErrorLoadingInterrupted加载时被中断有可能是因为addAllIdentificationPhoneNumbersToContext中数据处理出错,打断点调试一下

 

3)

Error Domain=com.apple.CallKit.error.calldirectorymanager Code=3CXErrorCodeCallDirectoryManagerErrorEntriesOutOfOrder可能是因为加载数据格式错误比如号码中带有符号,号码没有增序排列

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){
        @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 *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;
}

 

 

 

转载于:https://www.cnblogs.com/StevenHuSir/p/CallKit_CallRemind.html

你可能感兴趣的:(CallKit详解(来电提醒+骚扰拦截))