通讯录简介
通讯录使用场景:
- 电商类的 App,设置收货人电话号码。
- 即时通讯类 App,添加手机联系人好友。
通讯录获取方案:
一、iOS 9 以前的通讯录框架
-
AddressBookUI.framework
框架- 提供了联系人列表界面、联系人详情界面、添加联系人界面等。
- 一般用于选择联系人。
-
AddressBook.framework
框架- 纯 C 语言的 API,仅仅是获得联系人数据。
- 没有提供 UI 界面展示,需要自己搭建联系人展示界面。
- 里面的数据类型大部分基于 Core Foundation 框架,使用起来炒鸡复杂。
二、 iOS 9 以后最新通讯录框架
-
ContactsUI.framework
框架。- 拥有
AddressBookUI.framework
框架的所有功能,使用起来更加的面向对象。
- 拥有
-
Contacts.framework
框架。- 拥有
AddressBook.framework
框架的所有功能,不再是 C 语言的 API,使用起来非常简单。
- 拥有
iOS 9 以前的通讯录框架
AddressBookUI
实现步骤
一、创建选择联系人的控制器
// 创建联系人选择控制器
ABPeoplePickerNavigationController *pvc = [[ABPeoplePickerNavigationController alloc] init];
二、设置代理(用来接收用户选择的联系人信息)
// 设置代理
pvc.peoplePickerDelegate = self;
三、弹出联系人控制器
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
{
ABAddressBookRef bookRef = ABAddressBookCreate();
ABAddressBookRequestAccessWithCompletion(bookRef, ^(bool granted, CFErrorRef error) {
if (granted)
{
NSLog(@"授权成功!");
[self presentViewController:pvc animated:YES completion:nil];
}
else
{
NSLog(@"授权失败!");
}
});
}
else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
{
[self presentViewController:pvc animated:YES completion:nil];
}
四、实现代理方法
// 选择某个联系人时调用
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person
{
NSLog(@"选中联系人");
CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
NSString *fir = CFBridgingRelease(firstName);
NSString *las = CFBridgingRelease(lastName);
NSLog(@"%@---%@", fir, las);
ABMultiValueRef multi = ABRecordCopyValue(person, kABPersonPhoneProperty);
CFIndex count = ABMultiValueGetCount(multi);
for (int i = 0; i < count; i++)
{
NSString *label = (__bridge_transfer NSString *)ABMultiValueCopyLabelAtIndex(multi, i);
NSString *phone =(__bridge_transfer NSString *) ABMultiValueCopyValueAtIndex(multi, i);
NSLog(@"%@---%@", label, phone);
}
}
Core Foundation 对象手动管理内存,如果是 Create、Copy、Retain 等字样创建的对象,需要手动 CFRelease。类似 Objective-C 的 MRC。
拓展:__bridge
,__bridge_retained
和 __bridge_transfer
三个转换关键字的区别。
-
__bridge
只做类型转换,但是不修改对象(内存)管理权; -
__bridge_retained
(也可以使用CFBridgingRetain
)将 Objective-C 的对象转换为 Core Foundation 的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease
或者相关方法来释放对象; -
__bridge_transfer
(也可以使用CFBridgingRelease
)将 Core Foundation 的对象转换为 Objective-C 的对象,同时将对象(内存)的管理权交给 ARC。
五、在对应的代理方法中获取联系人信息
// 1.选择联系人时使用(不展开详情)
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person;
// 2.选择联系人某个属性时调用(展开详情)
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier;
// 3.取消选中联系人时调用
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker;
注意:选择联系人的不展开详情(代理方法1)和展开详情(代理方法2)的代理方法都写了的时候,展开详情的代理方法就不执行。
AddressBook
一、请求授权
从 iOS 6 开始,需要得到用户的授权才能访问通讯录,因此在使用之前,需要检查用户是否已经授权。
// 获得通讯录的授权状态
ABAddressBookGetAuthorizationStatus()
授权状态
用户还没有决定是否授权你的程序进行访问:
kABAuthorizationStatusNotDetermined
iOS 设备上一些许可配置阻止程序与通讯录数据库进行交互:
kABAuthorizationStatusRestricted
用户明确的拒绝了你的程序对通讯录的访问:
kABAuthorizationStatusDenied
用户已经授权给你的程序对通讯录进行访问:
kABAuthorizationStatusAuthorized
// 判断当前的授权状态是否是用户还未选择的状态
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
{
ABAddressBookRef bookRef = ABAddressBookCreate();
ABAddressBookRequestAccessWithCompletion(bookRef, ^(bool granted, CFErrorRef error) {
if (granted)
{
NSLog(@"授权成功!");
}
else
{
NSLog(@"授权失败!");
}
});
}
二、判断授权状态
如果已授权,则继续;未授权,则提示用户,并返回。
// 判断当前的授权状态
if (ABAddressBookGetAuthorizationStatus() != kABAuthorizationStatusAuthorized)
{
NSLog(@"您的通讯录暂未允许访问,请去设置->隐私里面授权!");
return;
}
三、创建通讯录对象
// 创建通讯录对象
ABAddressBookRef bookRef = ABAddressBookCreate();
四、从通信录对象中, 获取所有的联系人
// 获取通讯录中所有的联系人
CFArrayRef arrayRef = ABAddressBookCopyArrayOfAllPeople(bookRef);
五、遍历所有的联系人
// 遍历所有联系人
CFIndex count = CFArrayGetCount(arrayRef);
for (int i = 0; i < count; i++)
{
ABRecordRef record = CFArrayGetValueAtIndex(arrayRef, i);
// 获取姓名
NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty);
NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(record, kABPersonLastNameProperty);
NSLog(@"firstName = %@, lastName = %@", firstName, lastName);
// 获取电话号码
ABMultiValueRef multiValue = ABRecordCopyValue(record, kABPersonPhoneProperty);
CFIndex count = ABMultiValueGetCount(multiValue);
for (int i = 0; i < count; i ++)
{
NSString *label = (__bridge_transfer NSString *)ABMultiValueCopyLabelAtIndex(multiValue, i);
NSString *phone = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(multiValue, i);
NSLog(@"label = %@, phone = %@", label, phone);
}
CFRelease(multiValue);
}
六、释放不再使用的对象
CFRelease(bookRef);
CFRelease(arrayRef);
联系人属性定义
所有的属性常量值都定义在了 ABPerson.h
头文件中。
联系人属性包括以下类型:
- 简单属性:姓、名等
- 多重属性:电话号码、电子邮件等
- 组合属性:地址等
注意:使用
ABRecordCopyValue
可以从一条 Person 记录中获取到对应的记录,但是后续处理则需要根据记录的具体类型加以区分。
简单属性
一个联系人就是一个 ABRecordRef
,每个联系人都有自己的属性,比如名字、电话、邮件等。
使用 ABRecordCopyValue
函数可以从 ABRecordRef
中获得联系人的简单属性(例如:一个字符串)。
ABRecordCopyValue
函数接收 2 个参数。
第 1 个参数是 ABRecordRef
实例。
第 2 个参数是属性关键字,定义在 ABPerson.h
中。
ABPersonCopyLocalizedPropertyName
函数可以根据指定的关键字获取对应的标签文本。
获得所有的联系人数据
// 获取所有联系人记录
CFArrayRef array = ABAddressBookCopyArrayOfAllPeople(addressBook);
NSInteger count = CFArrayGetCount(array);
for (NSInteger i = 0; i < count; ++i) {
// 取出一条记录
ABRecordRef person = CFArrayGetValueAtIndex(array, i);
// 取出个人记录中的详细信息
// 名
CFStringRef firstNameLabel = ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty);
CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
CFStringRef lastNameLabel = ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty);
// 姓
CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName);
}
CoreFoundation 与 Foundation之间的桥接
// 1. 获取通讯录引用
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);
// 2. 获取所有联系人记录
NSArray *array = (__bridge NSArray *)(ABAddressBookCopyArrayOfAllPeople(addressBook));
for (NSInteger i = 0; i < array.count; i++) {
// 取出一条记录
ABRecordRef person = (__bridge ABRecordRef)(array[i]);
// 取出个人记录中的详细信息
NSString *firstNameLabel = (__bridge NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty));
NSString *firstName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonFirstNameProperty));
NSString *lastNameLabel = (__bridge NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty));
NSString *lastName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonLastNameProperty));
NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName);
}
CFRelease(addressBook);
多重属性
联系人的有些属性值就没这么简单,一个属性可能会包含多个值
比如邮箱,分为工作邮箱、住宅邮箱、其他邮箱等
比如电话,分为工作电话、住宅电话、其他电话等
如果是复杂属性,那么 ABRecordCopyValue
函数返回的就是 ABMultiValueRef
类型的数据,例如邮箱或者电话
// 取电话号码
ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
// 取记录数量
NSInteger phoneCount = ABMultiValueGetCount(phones);
// 遍历所有的电话号码
for (NSInteger i = 0; i < phoneCount; i++)
{
}
获取复杂属性的方法
// 电话标签
CFStringRef phoneLabel = ABMultiValueCopyLabelAtIndex(phones, i);
// 本地化电话标签
CFStringRef phoneLocalLabel = ABAddressBookCopyLocalizedLabel(phoneLabel);
// 电话号码
CFStringRef phoneNumber = ABMultiValueCopyValueAtIndex(phones, i);
添加联系人的步骤
添加联系人的步骤:
- 通过
ABPersonCreate
函数创建一个新的联系人(返回ABRecordRef
)。 - 通过
ABRecordSetValue
函数设置联系人的属性。 - 通过
ABAddressBookAddRecord
函数将联系人添加到通讯录数据库中。 - 通过
ABAddressBookSave
函数保存刚才所作的修改。
可以通过 ABAddressBookHasUnsavedChanges
函数判断是否有未保存的修改
当决定是否更改通讯录数据库后,你可以分别使用 AbAddressBookSave
或 ABAddressBookRevert
方式来保存或放弃更改 。
添加群组的步骤
添加群组的步骤大体和添加联系人一致:
- 通过
ABPersonCreate
函数创建一个新的组。(返回ABRecordRef
) - 通过
ABRecordSetValue
函数设置组名。 - 通过
ABAddressBookAddRecord
函数将组添加到通讯录数据库中。 - 通过
ABAddressBookSave
函数保存刚才所作的修改。
操作联系人的头像
想操作联系人的头像,有以下函数
BPersonHasImageData
判断通讯录中的联系人是否有图片
ABPersonCopyImageData
取得图片数据(假如有的话)
ABPersonSetImageData
设置联系人的图片数据
通讯录的修改回调
// 创建通讯录
self.addressBook = ABAddressBookCreate();
// 注册通知
ABAddressBookRegisterExternalChangeCallback(self.addressBook, _addressBookChange, nil);
// 处理收到通知的 Action
void _addressBookChange(ABAddressBookRef addressBook, CFDictionaryRef info, void *context)
{
}
- (void)dealloc
{
// 注销通知
ABAddressBookUnregisterExternalChangeCallback(self.addressBook, _addressBookChange, nil);
// 释放对象
CFRelease(self.addressBook);
}
iOS 9 以后的通讯录新框架
iOS 9 之前操作通讯录还是比较麻烦的,iOS 9 以后苹果推出了全新的通讯录框架,使用起来更加的面向对象。
CNContactUI
实现步骤
一、创建选择联系人的控制器
// 创建联系人选择控制器
CNMutableContact *contact = [[CNMutableContact alloc] init];
CNLabeledValue *labelValue = [CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberMobile
ontact.phoneNumbers = @[labelValue]; value:[CNPhoneNumber phoneNumberWithStringValue:phoneNum]];
CNContactViewController *contactController = [CNContactViewController viewControllerForNewContact:contact];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:contactController];
二、设置代理(用来接收用户选择的联系人信息)
// 设置代理
contactController.delegate = self;
三、弹出联系人控制器
[controller presentViewController:nav animated:YES completion:nil];
四、实现代理方法
// 选择某个联系人时调用
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty
{
CNContact *contact = contactProperty.contact;
NSString *name = [CNContactFormatter stringFromContact:contact style:CNContactFormatterStyleFullName];
CNPhoneNumber *phoneValue= contactProperty.value;
NSString *phoneNumber = phoneValue.stringValue;
NSLog(@"%@--%@",name, phoneNumber);
}
五、在对应的代理方法中获取联系人信息
// 1.选择联系人时使用(不展开详情)
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact;
// 2.选择联系人某个属性时调用(展开详情)
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty;
// 3.取消选中联系人时调用
- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker;
注意:与 AddressBookUI 一样,选择联系人的不展开详情(代理方法1)和展开详情(代理方法2)的代理方法都写了的时候,展开详情的代理方法就不执行。
CNContact
实现步骤
一、请求授权
// 获得通讯录的授权状态
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts]
授权状态
用户还没有决定是否授权你的程序进行访问:
CNAuthorizationStatusNotDetermined
iOS 设备上一些许可配置阻止程序与通讯录数据库进行交互:
CNAuthorizationStatusRestricted
用户明确的拒绝了你的程序对通讯录的访问:
CNAuthorizationStatusDenied
用户已经授权给你的程序对通讯录进行访问:
CNAuthorizationStatusAuthorized
// 判断当前的授权状态是否是用户还未选择的状态
if (status == CNAuthorizationStatusNotDetermined)
{
CNContactStore *store = [CNContactStore new];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted)
{
NSLog(@"授权成功!");
}
else
{
NSLog(@"授权失败!");
}
}];
}
二、判断授权状态
如果已授权,则继续;未授权,则提示用户,并返回。
// 判断当前的授权状态
if (status != CNAuthorizationStatusAuthorized)
{
NSLog(@"您的通讯录暂未允许访问,请去设置->隐私里面授权!");
return;
}
三、创建通讯录对象
// 创建通讯录对象
CNContactStore *contactStore = [CNContactStore new];
四、设置访问的属性 Key,每个 Key 对应一个属性,iOS 9 新增,如果没有设置,访问该属性就会崩溃。
// 姓名前缀
CNContactNamePrefixKey
// 名
CNContactGivenNameKey
// 中间名
CNContactMiddleNameKey
// 姓
CNContactFamilyNameKey
// 婚前姓
CNContactPreviousFamilyNameKey
// 姓名后缀
CNContactNameSuffixKey
// 昵称
CNContactNicknameKey
// 公司
CNContactOrganizationNameKey
// 部门
CNContactDepartmentNameKey
// 职位
CNContactJobTitleKey
// 名字拼音或音标
CNContactPhoneticGivenNameKey
// 中间名拼音或音标
CNContactPhoneticMiddleNameKey
// 姓拼音或音标
CNContactPhoneticFamilyNameKey
// 公司拼音或音标
CNContactPhoneticOrganizationNameKey
// 生日
CNContactBirthdayKey
// 农历
CNContactNonGregorianBirthdayKey
// 备注
CNContactNoteKey
// 图片
CNContactImageDataKey
// 缩略图
CNContactThumbnailImageDataKey
// 图片是否允许访问
CNContactImageDataAvailableKey
// 类型
CNContactTypeKey
// 号码
CNContactPhoneNumbersKey
// 电子邮件
CNContactEmailAddressesKey
// 地址
CNContactPostalAddressesKey
// 日期
CNContactDatesKey
// URL
CNContactUrlAddressesKey
// 关联人
CNContactRelationsKey
// 社交
CNContactSocialProfilesKey
// 即时通讯
CNContactInstantMessageAddressesKey
NSArray *keys = @[CNContactPhoneNumbersKey,CNContactGivenNameKey];
五、从通信录对象中, 获取所有的联系人,并遍历
// 获取通讯录中所有的联系人
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
[contactStore enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
// 获取姓名
NSString *firstName = contact.familyName;
NSString *lastName = contact.givenName;
NSLog(@"%@--%@",firstName,lastName);
// 获取电话号码
for (CNLabeledValue *labeledValue in contact.phoneNumbers)
{
CNPhoneNumber *phoneValue = labeledValue.value;
NSString *phoneNumber = phoneValue.stringValue;
NSString *label = [CNLabeledValue localizedStringForLabel:labeledValue.label];
NSLog(@"%@--%@",label,phoneNumber);
}
}];
通讯录的修改回调
// 注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_contactStoreDidChange) name:CNContactStoreDidChangeNotification object:nil]
// 处理收到通知的 Action
- (void)_contactStoreDidChange
{
}
- (void)dealloc
{
// 注销通知
[[NSNotificationCenter defaultCenter] removeObserver:self name:CNContactStoreDidChangeNotification object:nil];
}
当 App 活跃(前台+后台活动期间)的时候,当通讯录修改的时候,会收到通知
当 App 不活跃的时候(挂起的时候),App 收不到通知;而是,当 App 到前台的时候收到延迟的通知。
LJContactManager
介绍
LJContanctManager 是我写的一款操作通讯录的类库,iOS 9 之前使用的是 AddressBook 和 AddressBookUI 系统库,iOS 9 之后使用苹果新推出的 Contacts 和 ContactsUI 框架。
安装
CocoaPods
- 在 Podfile 中添加
pod 'LJContactManager'
。 - 执行
pod install
或pod update
。 - 导入 <LJContactManager.h>。
手动安装
- 下载 LJContactManager 文件夹内的所有内容。
- 将 LJContactManager 内的源文件添加(拖放)到你的工程。
- 导入
LJContactManager.h
。
使用
主要提供以下的方法:
- 选择联系人
/**
选择联系人
@param controller 控制器
@param completcion 回调
*/
- (void)selectContactAtController:(UIViewController *)controller
complection:(void (^)(NSString *name, NSString *phone))completcion;
- 创建新联系人
/**
创建新联系人
@param phoneNum 手机号
@param controller 当前 Controller
*/
- (void)createNewContactWithPhoneNum:(NSString *)phoneNum controller:(UIViewController *)controller;
- 添加到现有联系人
/**
添加到现有联系人
@param phoneNum 手机号
@param controller 当前 Controller
*/
- (void)addToExistingContactsWithPhoneNum:(NSString *)phoneNum controller:(UIViewController *)controller;
- 获取联系人列表(未分组的通讯录)
/**
获取联系人列表(未分组的通讯录)
@param completcion 回调
*/
- (void)accessContactsComplection:(void (^)(BOOL succeed, NSArray <LJPerson *> *contacts))completcion;
- 获取联系人列表(已分组的通讯录)
/**
获取联系人列表(已分组的通讯录)
@param completcion 回调
*/
- (void)accessSectionContactsComplection:(void (^)(BOOL succeed, NSArray <LJSectionPerson *> *contacts, NSArray <NSString *> *keys))completcion;
- 通讯录变更回调(未分组的通讯录)
/**
通讯录变更回调(未分组的通讯录)
*/
@property (nonatomic, copy) void (^contactsChangeHanlder) (BOOL succeed, NSArray <LJPerson *> *newContacts);
- 通讯录变更回调(已分组的通讯录)
/**
通讯录变更回调(已分组的通讯录)
*/
@property (nonatomic, copy) void (^sectionContactsHanlder) (BOOL succeed, NSArray <LJSectionPerson *> *newSectionContacts, NSArray <NSString *> *keys);
最后
由于笔者水平有限,文中如果有错误的地方,或者有更好的方法,还望大神指出。
附上本文的所有 demo 下载链接,【GitHub】。
如果你看完后觉得对你有所帮助,还望在 GitHub 上点个 star。赠人玫瑰,手有余香。