title: AddressBook.framework
date: 2016-08-22 17:44:14
tags: Frameworks
thumbnail:
http://www.51wendang.com/pic/00a226f3c9fa6ed45d1d781c/1-817-png_6_0_0_300_110_360_270_892.5_1263-1200-0-0-1200.jpg
AddressBook.framework/AddressBookUI.framework
9.0之后,
AddressBook.framework
被
Contacts.framework
代替。但是目前大部分的应用软件起支撑的版本是iOS6.0或7.0, 所以
AddressBook
还大有用处。之前在简书的文章
AddressBook, AddressBookUI中有提及, 但是由于是转载, 所以代码不是很清晰, 而且有一些读者希望得到清晰的代码以及详细的功能解释, 所以在这里把
AddressBook.framework
以及
AddressBookUI.framework
重新做一下详细的使用方法介绍。
这篇文章先介绍
AddressBook.framework
。
AddressBookUI
以及
Contacts.framework
也会补上。
开始使用AdressBook
首先, 导入我们需要的Framework
#import
经常使用到:
- ABAddressBookRef: 通讯录引用
- ABRecordRef: 记录引用
- ABPropertyID: 记录的属性ID
获取AddressBook使用权限
像使用相机、推送一样, 访问AddressBook同样需要获取权限。
在获取权限之前,我们需要创建一个AddressBook的引用,用来做后续操作。
static ABAddressBookRef r;
static inline ABAddressBookRef getAddressBookRef() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFErrorRef errorRef;
r = ABAddressBookCreateWithOptions(NULL, &errorRef);
if ( errorRef ) {
ILog(@"%@", (__bridge NSString *)CFErrorCopyFailureReason(errorRef));
}
});
return r;
}
ABAddressBookRef,作为后续使用的通讯录引用,这里写作单例。或者可以写作单例的属性,但只需初始化一次。
ABAddressBookCreateWithOptions
通过参数创建, options暂时为预留字段,但是AddressBook在9.0被弃用,估计不会被使用了。
在获取到引用之后,我们应先查询AddressBook的访问状态:
Boolean needRequestAccess() {
ABAuthorizationStatus s = ABAddressBookGetAuthorizationStatus();
if ( s == kABAuthorizationStatusDenied ||
s == kABAuthorizationStatusRestricted ) {
alert(@"提示", @"");
return false;
}
if ( s != kABAuthorizationStatusAuthorized ) {
return true;
}
return false;
}
推荐使用switch-case来判断状态。如果结果为
kABAuthorizationStatusAuthorized
表示可以正常访问。要注意的是,如果用户拒绝了首次的请求,那么需要用户在
设置-隐私-通讯录
中手动打开App使用通讯录的权限。
如果结果为
kABAuthorizationStatusNotDetermined
意味着我们需要请求访问权限:
Restricted
意味着系统决定了访问权限,用户不能修改。
void requestAddressBookAccess (ABAddressBookRequestAccessCompletionHandler handler) {
ABAddressBookRequestAccessWithCompletion(getAddressBookRef(), handler);
}
综合起来的调用:
void initAddressBook (void (^shouldAccessAddressBook)(Boolean boo)) {
if ( needRequestAccess() ) {
requestAddressBookAccess(^(bool granted, CFErrorRef error) {
if ( granted ) {
shouldAccess = true;
}
shouldAccessAddressBook(granted);
});
} else {
shouldAccess = true;
shouldAccessAddressBook(true);
}
}
非常简单的,我们获得了通讯录的访问权限。
用户的查询
用户的查询非常简单;
- ABAddressBookGetPersonCount:获取总人数
- ABAddressBookGetPersonWithRecordID:通过RecordID获取单个人,recordID可以通过
ABRecordGetRecordID
获得
- ABAddressBookCopyArrayOfAllPeople:获取全部联系人数组
- ABAddressBookCopyArrayOfAllPeopleInSource:获取记录引用中所有的练习嗯
- ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering:带着排序参数
- ABAddressBookCopyPeopleWithName:拷贝符合该名称的联系人
获取联系人的数量:
CFIndex i = ABAddressBookGetPersonCount(getAddressBookRef());
printf("AddressBook: has %ld Person", i);
获取联系人:
CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef());
ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0);
ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty));
获取联系人组使用
ABAddressBookCopyArrayOfAllGroups
即可。然后通过
ABAddressBookCopyArrayOfAllPeopleInSource
即可获得组内联系人。
用户信息的修改与删除
CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef());
ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0);
ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty));
if ( ABRecordSetValue(pr, kABPersonFirstNameProperty, (__bridge CFStringRef)@"Hello", nil) ) {
if ( ABAddressBookSave(getAddressBookRef(), nil) ) {
ILog(@"Succeed!");
}
}
if ( ABAddressBookRemoveRecord(getAddressBookRef(), pr, nil) ) {
if ( ABAddressBookSave(getAddressBookRef(), nil) ) {
ILog(@"Remove Succeed!");
}
}
ABRecordCopyValue
用来通过
ABPropertyID
获取相应属性的内容, 具体的ID在
ABPerson.h
中有详细列表。
ABRecordSetValue
用来通过
ABPropertyID
设置相应的属性内容,同时返回bool值以供判段。参数中的error已经不在使用。
在修改后,切记保存修改,使用
ABAddressBookSave
做保存,在这之前,可以使用
ABAddressBookHasUnsavedChanges
判断是否存在未保存的修改。
使用
ABAddressBookRemoveRecord
来移除记录。
监听其他应用对AddressBook的修改
在操作AddressBook的同时,有可能在后台的时候被其他程序所修改,addressBook提供了监听方法:
void callBack(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
ILog(@"AddressBook has changed in another application.");
};
void handleChange() {
ABAddressBookRegisterExternalChangeCallback(getAddressBookRef(), callBack, nil);
}
在收到监听后我们需要做相应的处理,比如: 是否其他改动与我们的改动有重叠等。
注意点
- 在设置值的时候,可以使用
ABMultiValueRef
设置多个值,比如说多个电话。
- 在不使用的时候,要使用
ABAddressBookUnregisterExternalChangeCallback
取消对AddressBook的监听。
Q&A:
Q:如何在删除完联系人的多个电话后,直接删除联系人?
A:通过propertyID获取到电话的信息后,做简单的判断就可以实现。如果仅存这一条电话记录,那么在删除的同时也删除掉联系人即可。
附
本文全部的代码
#import "Ins_AddressBook.h"
@implementation InsAddressBook
static Boolean shouldAccess;
static ABAddressBookRef r;
static inline ABAddressBookRef getAddressBookRef() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFErrorRef errorRef;
r = ABAddressBookCreateWithOptions(NULL, &errorRef);
if ( errorRef ) {
ILog(@"%@", (__bridge NSString *)CFErrorCopyFailureReason(errorRef));
}
});
return r;
}
void requestAddressBookAccess (ABAddressBookRequestAccessCompletionHandler handler) {
ABAddressBookRequestAccessWithCompletion(getAddressBookRef(), handler);
}
Boolean needRequestAccess() {
ABAuthorizationStatus s = ABAddressBookGetAuthorizationStatus();
if ( s == kABAuthorizationStatusDenied ||
s == kABAuthorizationStatusRestricted ) {
alert(@"提示", @"你之前已经拒绝了程序的访问权限, 请在设置-隐私-通讯录中手动打开, 并重新启动应用。");
return false;
}
if ( s != kABAuthorizationStatusAuthorized ) {
return true;
}
return false;
}
void initAddressBook (void (^shouldAccessAddressBook)(Boolean boo)) {
if ( needRequestAccess() ) {
requestAddressBookAccess(^(bool granted, CFErrorRef error) {
if ( granted ) {
shouldAccess = true;
}
shouldAccessAddressBook(granted);
});
} else {
shouldAccess = true;
shouldAccessAddressBook(true);
}
}
void callBack(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
ILog(@"AddressBook has changed in another application.");
};
void handleChange() {
ABAddressBookRegisterExternalChangeCallback(getAddressBookRef(), callBack, nil);
}
void getPerson () {
CFIndex i = ABAddressBookGetPersonCount(getAddressBookRef());
printf("AddressBook: has %ld Person", i);
CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef());
ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0);
ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty));
if ( ABRecordSetValue(pr, kABPersonFirstNameProperty, (__bridge CFStringRef)@"Hello", nil) ) {
if ( ABAddressBookSave(getAddressBookRef(), nil) ) {
ILog(@"Succeed!");
}
}
if ( ABAddressBookRemoveRecord(getAddressBookRef(), pr, nil) ) {
if ( ABAddressBookSave(getAddressBookRef(), nil) ) {
ILog(@"Remove Succeed!");
}
}
}
- (instancetype)init {
self = [super init];
if ( self ) {
initAddressBook(^(Boolean boo){
if ( boo ) {
ILog(@"Get access to addressBook")
handleChange();
getPerson();
} else {
ILog(@"Without access to addressBook")
}
});
}
return self;
}
DEF_SINGLETON_AUTOLOAD(InsAddressBook)
@end
AddressBookUI.framework
相比而言,使用UI则简单的多,直接进入创建一个新的用户:
ABNewPersonViewController * pv = [[ABNewPersonViewController alloc] init];
pv.newPersonViewDelegate = self;
[getRootNc() presentViewController:INS_NAV(pv) animated:YES completion:nil];
- (void) newPersonViewController: (ABNewPersonViewController *) newPersonView
didCompleteWithNewPerson: (ABRecordRef) person {
[newPersonView.navigationController dismissViewControllerAnimated:YES completion:nil];
}
创建界面与其代理方法,当然在
ABNewPersonViewController
中,有写参数我们可以设置,意义很简单,可以通过我们上边AddressBook.framework中获得的一些引用穿进去,或者在创建用户之前直接设置一个用户的基础信息。这里不做赘述。
选择一个用户
ABPeoplePickerNavigationController * pv = [[ABPeoplePickerNavigationController alloc] init];
pv.peoplePickerDelegate = self;
[getRootNc() presentViewController:pv animated:YES completion:nil];
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person {
}
// Called after the user has pressed cancel.
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
[peoplePicker dismissViewControllerAnimated:YES completion:nil];
}
注:这里我没有低版本的测试环境,但是可以看到,选中人的方法在8.0后才被使用,所以低版本的童鞋应该主动尝试原本被弃用的2个方法。
展示用户
ABPersonViewController
If displayedPerson has been added to an ABAddressBook, then the addressBook property will be updated to use the displayedPerson's ABAddressBook.
可以设置的属性包括:允许编辑、允许操作(短信、邮件等),允许展示连接的联系人,设置属性高亮等。也比较方便。
信息的完善
ABUnknownPersonViewController
ABUnknownPersonViewController *un = [[ABUnknownPersonViewController alloc] init];
un.displayedPerson = person; // 展示的联系人
un.allowsAddingToAddressBook = YES; // 允许添加到通讯录中
[getRootNc() presentViewController:INS_NAV(un) animated:YES completion:nil];
总结
AddressBook.framework、AdressBookUI.framework还是可以满足一些基本需求,但是由于是c库,并且功能不是很完善,所以在iOS9.0之后苹果使用Contacts.framework来代替AddressBook.framework。ContactsFramework是一整套OC的库,理解起来也很简单。
这是ContactsFramework中包含的一些头文件,在使用AddressBook的时候,基本所有的方法都在后边写了使用ContactsFramework中什么方法来代替。
CNContact
以及
CNGroup
分别代表了联系人与联系人组,比之前的Record引用清晰了许多。
CNContactFetchRequest
以及
CNSaveRequest
方便的提供了查询以及保存等操作。
CNSaveRequest
则提供了方便直观的方法去保存用户。
然后整个框架都清晰了很多,基本的使用方法如下:
CNContactStore * c;
CNAuthorizationStatus s;
CNContactFetchRequest * f;
NSError * e;
s = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
c = [[CNContactStore alloc] init];
if ( s != CNAuthorizationStatusAuthorized ) {
ILog(@"Un Authorized !");
[c requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * error) {
}];
}
// 根据`CNContact`中的属性单独获得,比如 @[CNContactGivenNameKey, CNContactMiddleNameKey, ...]
f = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactMiddleNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey]];
BOOL b = [c enumerateContactsWithFetchRequest:f error:&e usingBlock:^(CNContact * contact, BOOL * stop) {
/*
!$_, value=>"
), emailAddresses=(
"!$_, value=dylan@china.com>"
), postalAddresses=(not fetched)>
*/
}];
if ( b ) {
ILog(@"Search success.");
}
当然不能所有的代码全部我贴出来,关于保存等功能,大家自行探索。
2016-8-23 上午10:00 copyRight@
dylan@china.com 欢迎转载。