获取通讯录的方式
- iOS9以前 使用
AddressBookUI.framework
,AddressBook.framework
- iOS9以后 使用
ContactsUI.framework
,Contacts.framework
简单的说明下,其中 AddressBookUI 和 ContactsUI 是弹出一个通讯录界面提供选择一条联系人信息并且是不需要手动授权, AddressBook 和 Contacts 是获取全部通讯录数据并且需要手动授权
注意在iOS10获取通讯录权限需主动在info.plist里添加上提示信息. 不然会崩溃. 在info.plistkey: Privacy - Contacts Usage Descriptionvalue:是否允许此App访问你的通讯录
联系人模型 ContactModel
ContactModel.h
#import <Foundation/Foundation.h>
@interface ContactModel : NSObject
/** num */
@property (nonatomic, copy) NSString *num;
/** 姓名 */
@property (nonatomic, copy) NSString *name;
- (instancetype)initWithName:(NSString *)name phoneNum:(NSString *)num;
@end
ContactModel.m
#import "ContactModel.h"
@implementation ContactModel
- (instancetype)initWithName:(NSString *)name phoneNum:(NSString *)num
{
if (self = [super init]) {
self.name = name;
self.num = num;
}
return self;
}
@end
工具类 ContactModel
ContactsTool.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "ContactModel.h"
typedef void(^ContactBlock)(ContactModel *contactsModel);
@interface ContactsTool : NSObject
+ (NSMutableArray *)getAllPhoneInfo;
- (void)getOnePhoneInfoWithUI:(UIViewController *)target callBack:(ContactBlock)block;
@end
ContactsTool.m
#import "ContactsTool.h"
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#import <Contacts/Contacts.h>
#import <ContactsUI/ContactsUI.h>
#define iOS9Later ([UIDevice currentDevice].systemVersion.floatValue >= 9.0f)
@interface ContactsTool ()<CNContactPickerDelegate, ABPeoplePickerNavigationControllerDelegate>
@property(nonatomic, strong) ContactModel *contactModel;
@property(nonatomic, copy) ContactBlock myBlock;
@end
@implementation ContactsTool
+ (NSMutableArray *)getAllPhoneInfo {
return iOS9Later ? [self getContactsFromContactsAll] : [self getContactsFromAddressBookAll];
}
- (void)getOnePhoneInfoWithUI:(UIViewController *)target callBack:(ContactBlock)block
{
if (iOS9Later) {
[self getContactsFromContactUI:target];
} else {
[self getContactsFromAddressBookUI:target];
}
self.myBlock = block;
}
#pragma mark - AddressBookUI
- (void)getContactsFromAddressBookUI:(UIViewController *)target {
ABPeoplePickerNavigationController *pickerVC = [[ABPeoplePickerNavigationController alloc] init];
pickerVC.peoplePickerDelegate = self;
[target presentViewController:pickerVC animated:YES completion:nil];
}
/**
* 当用户选择某一个联系人的某一个属性的时候会执行该方法
*
* @param person 选中的联系人
* @param property 选中的联系人的属性
* @param identifier 每一个属性都有一个对应的表示
*/
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person {
// 将CoreFoundation框架的对象转成Foundation框架对象,那么可以通过桥接的方式
// 如果是CoreFoundation框架中的对象,如果是通过copy或者create或者retain,必须对应有一个release
/*
__bridge type: 通过该桥接方式,那么CoreFoundation对应的对象需要手动来释放,Foundation框架的对象如果是在ARC环境下面,则不需手动释放
__bridge_transfer type: 通过该桥接方式,那么CoreFoundation对应的对象表示已经交给Foundation对象进行管理,如果是在ARC环境下面,不需要释放任何一个对象
*/
ABMultiValueRef phonesRef = ABRecordCopyValue(person, kABPersonPhoneProperty);
if (!phonesRef) { return; }
NSString *phoneValue = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phonesRef, 0);
CFStringRef lastNameRef = ABRecordCopyValue(person, kABPersonLastNameProperty);
CFStringRef firstNameRef = ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *lastname = (__bridge_transfer NSString *)(lastNameRef);
NSString *firstname = (__bridge_transfer NSString *)(firstNameRef);
NSString *name = [NSString stringWithFormat:@"%@%@", lastname == NULL ? @"" : lastname, firstname == NULL ? @"" : firstname];
NSLog(@"姓名: %@", name);
ContactModel *model = [[ContactModel alloc] initWithName:name phoneNum:phoneValue];
NSLog(@"电话号码: %@", phoneValue);
CFRelease(phonesRef);
if (self.myBlock) self.myBlock(model);
}
#pragma mark - ContactsUI
- (void)getContactsFromContactUI:(UIViewController *)target {
CNContactPickerViewController *pickerVC = [[CNContactPickerViewController alloc] init];
pickerVC.delegate = self;
[target presentViewController:pickerVC animated:YES completion:nil];
}
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact {
NSString *name = [NSString stringWithFormat:@"%@%@", contact.familyName == NULL ? @"" : contact.familyName, contact.givenName == NULL ? @"" : contact.givenName];
NSLog(@"姓名: %@", name);
CNPhoneNumber *phoneNumber = [contact.phoneNumbers[0] value];
ContactModel *model = [[ContactModel alloc] initWithName:name phoneNum:[NSString stringWithFormat:@"%@", phoneNumber.stringValue]];
NSLog(@"电话号码: %@", phoneNumber.stringValue);
if (self.myBlock) self.myBlock(model);
}
#pragma mark - AddressBook
+ (NSMutableArray *)getContactsFromAddressBookAll {
//取得通讯录访问授权状态
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
CFErrorRef myError = NULL;
// ABAddressBookRef 代表通讯对象
//调用ABAddressBookCreateWithOptions()方法创建通讯录对象ABAddressBookRef
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &myError);
if (myError) {
[self showErrorAlert];
if (addressBook) CFRelease(addressBook);
return nil;
}
/*
kABAuthorizationStatusNotDetermined = 0, 没有决定是否授权
kABAuthorizationStatusRestricted, 受限制
kABAuthorizationStatusDenied, 拒绝
kABAuthorizationStatusAuthorized 授权
*/
__block NSMutableArray *contactModels = [NSMutableArray array];
if (status == kABAuthorizationStatusNotDetermined) { // 用户还没有决定是否授权你的程序进行访问
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
if (granted) { //授权成功
contactModels = [self getAddressBookInfo:addressBook];
} else {
[self showErrorAlert];
if (addressBook) CFRelease(addressBook);
}
});
// 用户已拒绝 或 iOS设备上的家长控制或其它一些许可配置阻止程序与通讯录数据库进行交互
} else if (status == kABAuthorizationStatusDenied || status == kABAuthorizationStatusRestricted) {
[self showErrorAlert];
if (addressBook) CFRelease(addressBook);
} else if (status == kABAuthorizationStatusAuthorized) { // 用户已授权
contactModels = [self getAddressBookInfo:addressBook];
}
return contactModels;
}
/*
桥接有三种方式:
(__bridge type)(expression) : 只是让NSFoundation框架暂时使用CF框架对象,注意需要手动释放 Core Foundation 对象,用CFRelease( )函数。
(__bridge_transfer type)(expression) / CFBridgingRelease(expression) : CF框架移交对象的管理权给NSFoundation框架,不需要手动释放对象
前两种是将CF对象转NSFoundation,最后一个是NSFoundation转 CF对象,不常用
(__bridge_retained )()
*/
+ (NSMutableArray *)getAddressBookInfo:(ABAddressBookRef)addressBook {
CFArrayRef peopleArray = ABAddressBookCopyArrayOfAllPeople(addressBook);
NSInteger peopleCount = CFArrayGetCount(peopleArray);
NSMutableArray *contactModels = [NSMutableArray array];
for (int i = 0; i < peopleCount; i++) {
ABRecordRef person = CFArrayGetValueAtIndex(peopleArray, i);
ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
if (phones) {
NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *name = [NSString stringWithFormat:@"%@%@", lastName == NULL ? @"" : lastName, firstName == NULL ? @"" : firstName];
NSLog(@"姓名: %@", name);
CFIndex phoneCount = ABMultiValueGetCount(phones);
for (int j = 0; j < phoneCount; j++) {
NSString *phoneValue = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phones, j);
NSLog(@"电话号码: %@", phoneValue);
ContactModel *model = [[ContactModel alloc] initWithName:name phoneNum:phoneValue];
[contactModels addObject:model];
}
}
CFRelease(phones);
}
if (addressBook) CFRelease(addressBook);
if (peopleArray) CFRelease(peopleArray);
return contactModels;
}
#pragma mark - Contacts
+ (NSMutableArray *)getContactsFromContactsAll {
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
CNContactStore *store = [[CNContactStore alloc] init];
__block NSMutableArray *contactModels = [NSMutableArray array];
if (status == CNAuthorizationStatusNotDetermined) { // 用户还没有决定是否授权你的程序进行访问
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
contactModels = [self getContactsInfo:store];
} else {
[self showErrorAlert];
}
}];
// 用户已拒绝 或 iOS设备上的家长控制或其它一些许可配置阻止程序与通讯录数据库进行交互
} else if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {
[self showErrorAlert];
} else if (status == CNAuthorizationStatusAuthorized) { // 用户已授权
contactModels = [self getContactsInfo:store];
}
return contactModels;
}
+ (NSMutableArray *)getContactsInfo:(CNContactStore *)store {
NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
NSMutableArray *contactModels = [NSMutableArray array];
[store enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
NSString *name = [NSString stringWithFormat:@"%@%@", contact.familyName == NULL ? @"" : contact.familyName, contact.givenName == NULL ? @"" : contact.givenName];
NSLog(@"姓名: %@", name);
for (CNLabeledValue *labeledValue in contact.phoneNumbers) {
CNPhoneNumber *phoneNumber = labeledValue.value;
NSLog(@"电话号码: %@", phoneNumber.stringValue);
ContactModel *model = [[ContactModel alloc] initWithName:name phoneNum:phoneNumber.stringValue];
[contactModels addObject:model];
}
}];
return contactModels;
}
#pragma mark - Error
+ (void)showErrorAlert {
NSLog(@"授权失败, 请允许app访问您的通讯录, 在手机的”设置-隐私-通讯录“选项中设置允许");
}
@end
tips
将CoreFoundation框架的对象转成Foundation框架对象,那么可以通过桥接的方式
如果是CoreFoundation框架中的对象,如果是通过copy或者create或者retain,必须对应有一个releass
__bridge type: 通过该桥接方式,那么CoreFoundation对应的对象需要手动来释放,Foundation框架的对象如果是在ARC环境下面,则不需手动释放
__bridge_transfer type: 通过该桥接方式,那么CoreFoundation对应的对象表示已经交给Foundation对象进行管理,如果是在ARC环境下面,不需要释放任何一个对象
桥接有三种方式:
-
(__bridge type)(expression)
: 只是让NSFoundation框架暂时使用CF框架对象,注意需要手动释放 Core Foundation 对象,用CFRelease( )函数。 -
(__bridge_transfer type)(expression)
/ CFBridgingRelease(expression) `: CF框架移交对象的管理权给NSFoundation框架,不需要手动释放对象
前两种是将CF对象转NSFoundation, -
(__bridge_retained )(expression)
最后一个是NSFoundation转 CF对象,不常用
下面大致介绍一下通讯录操作中常用的类型:
-
ABAddressBookRef
:代表通讯录对象,通过该对象开发人员不用过多的关注通讯录的存储方式,可以直接以透明的方式去访问、保存(在使用AddressBook.framework操作联系人时,所有的增加、删除、修改后都必须执行保存操作,类似于Core Data)等。 -
ABRecordRef
:代表一个通用的记录对象,可以是一条联系人信息,也可以是一个群组,可以通过- -
ABRecordGetRecordType()
函数获得具体类型。如果作为联系人(事实上也经常使用它作为联系人),那么这个记录记录了一个完整的联系人信息(姓名、性别、电话、邮件等),每条记录都有一个唯一的ID标示这条记录(可以通过ABRecordGetRecordID()函数获得)。 -
ABPersonRef
:代表联系人信息,很少直接使用,实际开发过程中通常会使用类型为“kABPersonType”的- -
ABRecordRef
来表示联系人(由此可见ABPersonRef其实是一种类型为“kABPersonType”的ABRecordRef) -
ABGroupRef
:代表群组,与ABPersonRef类似,很少直接使用ABGroupRef,而是使用类型为“kABGroupType”的ABRecordRef来表示群组,一个群组可以包含多个联系人,一个联系人也同样可以多个群组。
首先看一下常用的操作通讯录记录的方法:
由于通讯录操作的关键是对ABRecordRef的操作
-
ABPersonCreate()
:创建一个类型为“kABPersonType”的ABRecordRef。 -
ABRecordCopyValue()
:取得指定属性的值。 -
ABRecordCopyCompositeName()
:取得联系人(或群组)的复合信息(对于联系人则包括:姓、名、公司等信息,对于群组则返回组名称)。 -
ABRecordSetValue()
:设置ABRecordRef的属性值。注意在设置ABRecordRef的值时又分为单值属性和多值属性:单值属性设置只要通过ABRecordSetValue()方法指定属性名和值即可; - 多值属性则要先通过创建一个
ABMutableMultiValueRef
类型的变量,然后通过ABMultiValueAddValueAndLabel()
方法依次添加属性值,最后通过ABRecordSetValue()
方法将ABMutableMultiValueRef
类型的变量设置为记录值。 -
ABRecordRemoveValue()
:删除指定的属性值
通讯录的访问步骤一般如下:
- 调用
ABAddressBookCreateWithOptions()
方法创建通讯录对象ABAddressBookRef。 - 调用
ABAddressBookRequestAccessWithCompletion()
方法获得用户授权访问通讯录。 - 调用
ABAddressBookCopyArrayOfAllPeople()、ABAddressBookCopyPeopleWithName()
方法查询联系人信息。 - 读取联系人后如果要显示联系人信息则可以调用ABRecord相关方法读取相应的数据;如果要进行修改联系人信息,则可以使用对应的方法修改ABRecord信息,然后调用ABAddressBookSave()方法提交修改;如果要删除联系人,则可以调用ABAddressBookRemoveRecord()方法删除,然后调用ABAddressBookSave()提交修改操作。
- 也就是说如果要修改或者删除都需要首先查询对应的联系人,然后修改或删除后提交更改。如果用户要增加一个联系人则不用进行查询,直接调用ABPersonCreate()方法创建一个ABRecord然后设置具体的属性,调用ABAddressBookAddRecord方法添加即可。
获取通讯录状态(ios9.0以前的)
kABAuthorizationStatusNotDetermined = 0, 没有决定是否授权
kABAuthorizationStatusRestricted, 受限制
kABAuthorizationStatusDenied, 拒绝
kABAuthorizationStatusAuthorized 授权
至于iOS9以后的通讯录状态把kAB
换成CN
即可 ,比如拒绝状态由kABAuthorizationStatusDenied
->CNAuthorizationStatusDenied
,其他的类比 即可
还有一个最大的变化,就是iOS9以后 访问通讯录属性,要事先设置好,否则会奔溃
下面是列表
// 姓名前缀
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 = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
像我这个,就是声明了。姓,名,手机号。
备注:
1.上文中所指的以Ref结尾的对象事实上是该对象的指针(或引用),在C语言的框架中多数类型会以Ref结尾,这个类型本身就是一个指针,定义时不需要加“*”
2.通常方法中包含copy、create、new、retain等关键字的方法创建的变量使用之后需要调用对应的release方法释放。例如:使用ABPersonCreate();创建完ABRecordRef变量后使用CFRelease()方法释放。
3.在与很多C语言框架交互时可以都存在Obj-C和C语言类型之间的转化(特别是Obj-C和Core Foundation框架中的一些转化),此时可能会用到桥接,只要在强转之后前面加上”__bridge”即可,经过桥接转化后的类型不需要再去手动维护内存,也就不需要使用对应的release方法释放内存。
Demo
网友评论