AddressBook.framework

AddressBook.framework/AddressBookUI.framework

9.0之后, AddressBook.frameworkContacts.framework代替。但是目前大部分的应用软件起支撑的版本是iOS6.0或7.0, 所以AddressBook还大有用处。之前在简书的文章AddressBook, AddressBookUI中有提及, 但是由于是转载, 所以代码不是很清晰, 而且有一些读者希望得到清晰的代码以及详细的功能解释, 所以在这里把AddressBook.framework以及AddressBookUI.framework重新做一下详细的使用方法介绍。

这篇文章先介绍AddressBook.frameworkAddressBookUI以及Contacts.framework也会补上。

开始使用AdressBook

首先, 导入我们需要的Framework

1
#import <AddressBook/AddressBook.h>

经常使用到:

  • ABAddressBookRef: 通讯录引用
  • ABRecordRef: 记录引用
  • ABPropertyID: 记录的属性ID

获取AddressBook使用权限

像使用相机、推送一样, 访问AddressBook同样需要获取权限。

在获取权限之前,我们需要创建一个AddressBook的引用,用来做后续操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
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的访问状态:

1
2
3
4
5
6
7
8
9
10
11
12
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意味着系统决定了访问权限,用户不能修改。

1
2
3
void requestAddressBookAccess (ABAddressBookRequestAccessCompletionHandler handler) {
ABAddressBookRequestAccessWithCompletion(getAddressBookRef(), handler);
}

综合起来的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
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:拷贝符合该名称的联系人

获取联系人的数量:

1
2
CFIndex i = ABAddressBookGetPersonCount(getAddressBookRef());
printf("AddressBook: has %ld Person", i);

获取联系人:

1
2
3
4
CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef());
ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0);
ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty));

获取联系人组使用ABAddressBookCopyArrayOfAllGroups即可。然后通过ABAddressBookCopyArrayOfAllPeopleInSource即可获得组内联系人。

用户信息的修改与删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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提供了监听方法:

1
2
3
4
5
6
7
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获取到电话的信息后,做简单的判断就可以实现。如果仅存这一条电话记录,那么在删除的同时也删除掉联系人即可。

本文全部的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#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则简单的多,直接进入创建一个新的用户:

1
2
3
ABNewPersonViewController * pv = [[ABNewPersonViewController alloc] init];
pv.newPersonViewDelegate = self;
[getRootNc() presentViewController:INS_NAV(pv) animated:YES completion:nil];
1
2
3
4
- (void) newPersonViewController: (ABNewPersonViewController *) newPersonView
didCompleteWithNewPerson: (ABRecordRef) person {
[newPersonView.navigationController dismissViewControllerAnimated:YES completion:nil];
}

创建界面与其代理方法,当然在ABNewPersonViewController中,有写参数我们可以设置,意义很简单,可以通过我们上边AddressBook.framework中获得的一些引用穿进去,或者在创建用户之前直接设置一个用户的基础信息。这里不做赘述。

选择一个用户

1
2
3
ABPeoplePickerNavigationController * pv = [[ABPeoplePickerNavigationController alloc] init];
pv.peoplePickerDelegate = self;
[getRootNc() presentViewController:pv animated:YES completion:nil];
1
2
3
4
5
6
7
8
- (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

1
2
3
4
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则提供了方便直观的方法去保存用户。

然后整个框架都清晰了很多,基本的使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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) {
/*
<CNContact: 0x7f8f4fb86dc0: identifier=41592D45-CE20-44C8-95C5-C1FE464474A5:ABPerson, givenName=(not fetched), familyName=(not fetched), organizationName=(not fetched), phoneNumbers=(
"<CNLabeledValue: 0x7f8f4fb88dc0: identifier=243FA6B4-33AE-43A6-8567-AFE11518BBCC, label=_$!<Home>!$_, value=<CNPhoneNumber: 0x7f8f4fb88ca0: countryCode=us, digits=+8613088488288>>"
), emailAddresses=(
"<CNLabeledValue: 0x7f8f4fb87c30: identifier=3B1CADF7-967B-41A0-A575-EB0B7BA1BB5B, label=_$!<Home>!$_, value=dylan@china.com>"
), postalAddresses=(not fetched)>
*/
}];
if ( b ) {
ILog(@"Search success.");
}

当然不能所有的代码全部我贴出来,关于保存等功能,大家自行探索。

2016-8-23 上午10:00 copyRight@dylan@china.com 欢迎转载。

评论