iOS 获取系统通讯录AddressBook的名单遇到的坑

zz做开发已经有一段时间了, 最近换了新公司, 决定写一些东西记录一下开发中遇到的坑. 新的公司做的是企业通讯方面的, 首先遇到的坑就是获取手机通讯录的信息.
由于版本适配的问题, 这次采用的是AddressBook这个框架, 这个框架在iOS9以后已经被Contacts所代替. 以后用到这个在做记录吧,
废话不多说了, 这次要做的就是把通讯录的信息取出来然后自定义界面展示.第一步要做的就是引入框架头文件

import <AddressBook/AddressBook.h>

由于访问的是通讯录要获得用户授权所有我们要写一个方法获取用户的授权 直接贴上代码吧
新建一个model类, 做联系人的model
属性如下, 因为我只需要这几个所以就只写着几个了
#import "BaseModel.h"
#import <AddressBook/AddressBook.h> @interface ContactBookModel : BaseModel<NSCoding> @property (nonatomic, strong) NSString *name, *telephone, *lastName, *characterName; @property (nonatomic, strong) NSData *imageData; @property (nonatomic, assign) BOOL isSelect;

@end

下面是获取通讯录, 我们可以写一个类专门处理这个, 因为在通讯录的开发中 , 我们可以在不同的控制器里会用到这个
-(void)getrightFromUser { //这个变量用于记录授权是否成功,即用户是否允许我们访问通讯录 int __block tip=0; //声明一个通讯簿的引用 ABAddressBookRef addBook =nil; //创建通讯簿的引用 addBook=ABAddressBookCreateWithOptions(NULL, NULL); //创建一个出事信号量为0的信号 dispatch_semaphore_t sema=dispatch_semaphore_create(0); //申请访问权限 ABAddressBookRequestAccessWithCompletion(addBook, ^(bool greanted, CFErrorRef error) { //greanted为YES是表示用户允许,否则为不允许 if (!greanted) { tip=1; } //发送一次信号 dispatch_semaphore_signal(sema); }); //等待信号触发 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); if (tip) { //我们要在这里做一个提示框提示用户给了授权才能获取 return; } //这个是用来获取数据的 [self getPhoneContactAddress:addBook]; }
//获取手机通讯录中的信息
- (void)getPhoneContactAddress:(ABAddressBookRef )addBook { //手机通讯录获取存放的数组 self.contactArray = [NSMutableArray array]; //获取所有联系人的数组 CFArrayRef allLinkPeople = ABAddressBookCopyArrayOfAllPeople(addBook); //获取联系人总数 CFIndex number = ABAddressBookGetPersonCount(addBook); for (int i = 0; i < number; i++) { ABRecordRef people = CFArrayGetValueAtIndex(allLinkPeople, i); //获取当前联系人名字 NSStringfirstName=(__bridge NSString *)(ABRecordCopyValue(people, kABPersonFirstNameProperty)); //获取当前联系人姓氏 NSString**lastName=(__bridge NSString *)(ABRecordCopyValue(people, kABPersonLastNameProperty)); //获取当前联系人中间名 NSString *middleName=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonMiddleNameProperty)); // 获取联系人的头像 NSData *userImage=(__bridge NSData*)(ABPersonCopyImageData(people)); //注意这里的手机号是一个数组, 因为一人可以有很多个手机号, 这里我处理的是一个手机号对应一个姓名, 多个手机号就有多个相同的姓名不同手机号的模型 NSMutableArray * phoneArr = [[NSMutableArray alloc]init]; ABMultiValueRef phones= ABRecordCopyValue(people, kABPersonPhoneProperty); for (NSInteger j=0; j<ABMultiValueGetCount(phones); j++) { [phoneArr addObject:(__bridge NSString *)(ABMultiValueCopyValueAtIndex(phones, j))]; } for (int i = 0; i< phoneArr.count; i++) { ContactBookModel *contactModel = [[ContactBookModel alloc] init];//把名字拼接在一块 contactModel.name = [NSString stringWithFormat:@"%@%@%@", lastName, middleName, firstName];contactModel.name = [contactModel.name stringByReplacingOccurrencesOfString:@"(null)" withString:@""]; if (contactModel.name == nil || [contactModel.name isEqualToString:@""]) { contactModel.name = @"无姓名"; } contactModel.imageData = userImage; contactModel.telephone = (__bridge NSString *)(ABMultiValueCopyValueAtIndex(phones, i)); contactModel.telephone = [contactModel.telephone stringByReplacingOccurrencesOfString:@"-" withString:@""]; [self.contactArray addObject:contactModel]; } } }
//到这里未知已经把手机号 姓名跟头像获取到了, 接着要做的就是把获取到的处理了, 一般来说获取的全部联系人的信息并不会有很长时间, 真正的耗时就是在获取之后对数组进行处理的过程中

- (void)handleArray { //字典把获取到的数组分一下组 self.keyContactDic = [NSMutableDictionary dictionary]; self.keyArray = [NSMutableArray array]; for (ContactBookModel *model in self.contactArray) { NSString *lastName; if (![model.name isEqualToString:@""] || model.name == nil) { lastName =[self transform:[model.name substringToIndex:1]]; } NSString *ZIMU = @"^[A-Za-z]"; NSPredicate *regextestA = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", ZIMU]; BOOL result = [regextestA evaluateWithObject:lastName]; if (!result) { lastName = @"#"; } if (![self.keyArray containsObject:lastName]) { NSMutableArray *array = [NSMutableArray array]; [array addObject:model]; [self.keyContactDic setObject:array forKey:lastName]; [self.keyArray addObject:lastName]; } else { NSMutableArray *array = [self.keyContactDic objectForKey:lastName]; [array addObject:model]; // NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"characterName" ascending:YES]; // array = [NSMutableArray arrayWithArray:[array sortedArrayUsingDescriptors:@[descriptor]]]; } } NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:nil ascending:YES]; self.keyArray = [NSMutableArray arrayWithArray:[self.keyArray sortedArrayUsingDescriptors:@[descriptor]]] ; self.searchDic = [NSMutableDictionary dictionary]; [self.searchDic addEntriesFromDictionary:self.keyContactDic]; dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; }); NSMutableArray *array1 = [NSMutableArray array]; for (NSString *key in self.keyContactDic) { NSArray *array = self.searchDic[key]; for (id obj in array) { [array1 addObject:obj]; } } }
//判断中文还是英文
-(BOOL)IsChinese:(NSString *)str { for(int i=0; i< [str length];i++){ int a = [str characterAtIndex:i]; if( a > 0x4e00 && a < 0x9fff) { return YES; } } return NO; }
//汉子转换拼音 把汉子转为拼音进行排序是我遇到的最大的坑, 因为这个造成了二三十秒 卡顿 (大概有四千的通讯录), 最后求助了一个群里的大神给了一个c文件
- (NSString *)transform:(NSString *)chinese { if ( chinese == nil || [chinese isEqualToString:@""]) { return nil; }
if ([self IsChinese:chinese]) { char cc = pinyinFirstLetter([chinese characterAtIndex:0]); return [NSString stringWithFormat:@"%c",cc ].uppercaseString; } else { return [chinese substringToIndex:1].uppercaseString; } }

写到这里大概通讯录的大概就有了, 数组为全部的, 字典是排过序的, 接下来做的就是存入沙盒中, 因为我们不可能每次都过来获取通讯录, 我们归档模型, 存入cache
model的.h中遵守NSCoding 而在.m中实现
- (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.name forKey:@"name"]; [aCoder encodeObject:self.telephone forKey:@"telephone"]; [aCoder encodeObject:self.lastName forKey:@"lastName"]; [aCoder encodeObject:self.characterName forKey:@"characterName"]; [aCoder encodeObject:self.imageData forKey:@"imageData"]; [aCoder encodeBool:self.isSelect forKey:@"isSelect"]; }
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.name = [aDecoder decodeObjectForKey:@"name"]; self.telephone = [aDecoder decodeObjectForKey:@"telephone"]; self.lastName = [aDecoder decodeObjectForKey:@"lastName"]; self.characterName = [aDecoder decodeObjectForKey:@"characterName"]; self.imageData = [aDecoder decodeObjectForKey:@"imageData"]; self.isSelect = [aDecoder decodeBoolForKey:@"isSelect"]; } return self; }
//下面写到沙盒中
- (void)writeToSandBox { NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; NSString *conactPath = [cache stringByAppendingPathComponent:@"contactArray.text"]; BOOL exist = [[NSFileManager defaultManager] fileExistsAtPath:conactPath]; if (exist) { BOOL result = [[NSFileManager defaultManager] removeItemAtPath:conactPath error:nil]; if (result) { NSLog(@"移除成功"); } else { NSLog(@"移除失败"); } }
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //我这里把总的数组存进去了, 因为我要用的只是全部的数组就行, 如果有需要可以把字典也存进去 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.contactArray]; BOOL result = [data writeToFile:conactPath atomically:YES]; if (result) { NSLog(@"写入成功"); }else { NSLog(@"写入失败"); } }); }

//然后我们可以在用的地方存沙盒中取出来
//还没有完, 因为我们用户会修改通讯录的, 不过不要怕有修改的回调函数, 我们在appdelegate中写下如下
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //类似于观察者, 当用户修改了通讯录, 我们进入程序会有回调 ABAddressBookRef addresBook = ABAddressBookCreateWithOptions(NULL, NULL); ABAddressBookRegisterExternalChangeCallback(addresBook, addressBookChanged, (__bridge void *)(self)); return YES; }
void addressBookChanged(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) { //这是一个回调函数, 当通讯录发生改变的时候, 用户再次进来会有回调, 我们可以在这里做操作, 比如重新读取通讯录把老的数据从沙盒中删除, 然后把新的写入沙盒 }
//简书写文章排版也是一个大问题, 以后好好学下也要, 这样的排版我自己都不愿意看..... 我看看有没有上传文件的地方, 上传一份最终要的是c文件汉子转拼音的那个

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容