iOS通讯录整合,兼容iOS789写法,附demo
苹果的通讯录功能在iOS7,iOS8,iOS9 都有着一定的不同,iOS7和8用的是 <AddressBookUI/AddressBookUI.h> ,但是两个系统版本的代理方法有一些变化,有些代理方法都标注了 NS_DEPRECATED_IOS(2_0, 8_0) 并推荐了另一个代理方法与之对应。 而iOS8到iOS9则是直接弃用了<AddressBookUI/AddressBookUI.h>取而代之的是<ContactsUI/ContactsUI.h>,后者是OC调用,据说当时苹果宣布弃用AddressBookUI还引来了阵阵欢呼。这也就是在使用通讯录功能时得考虑版本各种判断,我也就是工作中遇到了这种坑,然后就顺手兼容封装了一下。希望能解决这个问题。
我觉得通讯录这里的类结构没必要像SDWebImage或是Core Location这样列出来详细去说。大家用到通讯录无外乎就三个功能:
1.点击弹出通讯录页面,选择了一个联系人的电话后直接将信息填到页面输入框内。
2.遍历所有的通讯录数据统一做批量操作,搭建新页面或直接上传。
3.给通讯录写入一条信息。
这里会先对比一下iOS789的写法,最后奉上demo(一个封装后的库,提供了非常便利的api)。不关心内部实现的朋友可以直接拉到demo部分。
一、首先是获取通讯录的权限
iOS7和8保持一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<span style=
"font-size: 13px; font-family: 宋体;"
> ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(
NULL
,
NULL
);
if
(status == kABAuthorizationStatusNotDetermined) {
NSLog
(@
"还没问"
);
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(
bool
granted, CFErrorRef error){
if
(granted){
NSLog
(@
"点击同意"
);
}
else
{
NSLog
(@
"点击拒绝"
);
}
});
}
else
if
(status == kABAuthorizationStatusAuthorized){
NSLog
(@
"已经授权"
);
[
self
loadPerson];
}
else
{
NSLog
(@
"没有授权"
);
// 弹窗提示去获取权限
}</span>
|
iOS9及以后调用方法改成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<span style=
"font-size: 13px; font-family: 宋体;"
> CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if
(status == CNAuthorizationStatusNotDetermined) {
[[[CNContactStore alloc]init] requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(
BOOL
granted,
NSError
* _Nullable error) {
NSLog
(@
"还没问"
);
if
(granted){
NSLog
(@
"点击了同意"
);
[
self
loadPerson];
}
else
{
NSLog
(@
"点击了拒绝"
);
}
}];
}
else
if
(status == CNAuthorizationStatusAuthorized){
NSLog
(@已经授权");
}
else
{
NSLog
(@
"没有授权"
);
} </span>
|
二、弹出通讯录选择界面
iOS7的写法如下,代理方法的返回值大多是BOOL类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<span style=
"font-size: 13px; font-family: 宋体;"
>- (
BOOL
)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
return
YES
;
}
- (
BOOL
)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
ABMultiValueRef phone = ABRecordCopyValue(person, kABPersonPhoneProperty);
long
index = ABMultiValueGetIndexForIdentifier(phone,identifier);
NSString
*phoneNO = (__bridge
NSString
*)ABMultiValueCopyValueAtIndex(phone, index);
CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString
*lastname = (__bridge_transfer
NSString
*)(lastName);
NSString
*firstname = (__bridge_transfer
NSString
*)(firstName);
if
(phone) {
[peoplePicker dismissViewControllerAnimated:
YES
completion:
nil
];
return
NO
;
}
return
YES
;
}
</span>
|
iOS8的代理方法换了,改成了下面两个,但是方法内部的取值基本相同
1
2
3
4
5
6
|
<span style=
"font-size: 13px; font-family: 宋体;"
>
// 点击了通讯录名字就会退出
- (
void
)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person;
// 点击了名字里面的电话或邮箱才会退出
- (
void
)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier;
</span>
|
至于会调用哪一个方法,可以根据实际需要去选择,在弹出界面的方法中predicateForSelectionOfPerson 这个属性传false就是调用下面的。
1
2
3
4
5
|
<span style=
"font-size: 13px; font-family: 宋体;"
> ABPeoplePickerNavigationController *pickervc = [[ABPeoplePickerNavigationController alloc] init];
pickervc.predicateForSelectionOfPerson = [
NSPredicate
predicateWithValue:
false
];
pickervc.peoplePickerDelegate =
self
;
[target presentViewController:pickervc animated:
YES
completion:
nil
];
</span>
|
iOS9系统下的弹出选择器方法 和 代理方法如下
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
|
<span style=
"font-size: 13px; font-family: 宋体;"
>
// 弹出选择器
- (
void
)presentPageOnTarget{
CNContactPickerViewController *contactVc = [[CNContactPickerViewController alloc] init];
contactVc.delegate =
self
;
[target presentViewController:contactVc animated:
YES
completion:
nil
];
}
// 代理方法
- (
void
)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact
{
SXPersonInfoEntity *personEntity = [SXPersonInfoEntity
new
];
NSString
*lastname = contact.familyName;
NSString
*firstname = contact.givenName;
NSLog
(@
"%@ %@"
, lastname, firstname);
personEntity.lastname = lastname;
personEntity.firstname = firstname;
NSMutableString
*fullname = [[
NSString
stringWithFormat:@
"%@%@"
,lastname,firstname] mutableCopy];
[fullname replaceOccurrencesOfString:@
"(null)"
withString:@
""
options:
NSCaseInsensitiveSearch
range:
NSMakeRange
(0, fullname.length)];
personEntity.fullname = fullname;
NSString
*fullPhoneStr = [
NSString
string];
NSArray
*phoneNums = contact.phoneNumbers;
for
(CNLabeledValue *labeledValue in phoneNums) {
NSString
*phoneLabel = labeledValue.label;
CNPhoneNumber *phoneNumer = labeledValue.value;
NSString
*phoneValue = phoneNumer.stringValue;
NSLog
(@
"%@ %@"
, phoneLabel, phoneValue);
if
(phoneValue.length > 0) {
fullPhoneStr = [fullPhoneStr stringByAppendingString:phoneValue];
fullPhoneStr = [fullPhoneStr stringByAppendingString:@
","
];
}
}
if
(fullPhoneStr.length > 1) {
personEntity.phoneNumber = [fullPhoneStr substringToIndex:fullPhoneStr.length - 1];
}
self
.chooseAction(personEntity);
}
</span>
|
这个是点击了名字就直接回调的方法,如果希望点击了属性再回调,则需要加上这一行
1
2
3
4
5
|
<span style=
"font-size: 13px; font-family: 宋体;"
>contactVc.predicateForSelectionOfContact = [
NSPredicate
predicateWithValue:
false
];
// 代理方法调用
- (
void
)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty
</span>
|
三、获取全部通讯录信息
关于批量获取所有通讯录信息的方法有点冗长,这里就不一一贴了,只贴下iOS9的写法,iOS7和8的代码demo里都有。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<span style=
"font-size: 13px; font-family: 宋体;"
>- (
void
)printAllPerson
{
// 获取
CNContactStore *contactStore = [[CNContactStore alloc] init];
NSArray
*keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
// 遍历
[contactStore enumerateContactsWithFetchRequest:request error:
nil
usingBlock:^(CNContact * _Nonnull contact,
BOOL
* _Nonnull stop) {
NSString
*lastname = contact.familyName;
NSString
*firstname = contact.givenName;
NSLog
(@
"%@ %@"
, lastname, firstname);
NSArray
*phoneNums = contact.phoneNumbers;
for
(CNLabeledValue *labeledValue in phoneNums) {
NSString
*phoneLabel = labeledValue.label;
CNPhoneNumber *phoneNumer = labeledValue.value;
NSString
*phoneValue = phoneNumer.stringValue;
NSLog
(@
"%@ %@"
, phoneLabel, phoneValue);
}
}];
}
</span>
|
四、写入通讯录
因为写入的话这个功能有点重量级,写入的时候要写入,名字、电话、email、地址等等,这就会使得api过于复杂。暂时我见到过的做法大多都是如果用户给了通讯录权限 那就给你插入一条名字+电话,我做了只有这两个入参的api,当然使用时也完全可以扩展成更多参数的。
iOS7和8
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
|
<span style=
"font-size: 13px; font-family: 宋体;"
>- (
void
)creatItemWithName:(
NSString
*)name phone:(
NSString
*)phone
{
if
((name.length < 1)||(phone.length < 1)){
NSLog
(@
"输入属性不能为空"
);
return
;
}
CFErrorRef error =
NULL
;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(
NULL
, &error);
ABRecordRef newRecord = ABPersonCreate();
ABRecordSetValue(newRecord, kABPersonFirstNameProperty, (__bridge CFTypeRef)name, &error);
ABMutableMultiValueRef multi = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(multi, (__bridge CFTypeRef)name, kABPersonPhoneMobileLabel,
NULL
);
ABRecordSetValue(newRecord, kABPersonPhoneProperty, multi, &error);
CFRelease(multi);
ABAddressBookAddRecord(addressBook, newRecord, &error);
ABAddressBookSave(addressBook, &error);
CFRelease(newRecord);
CFRelease(addressBook);
}
</span>
|
iOS9下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<span style=
"font-size: 13px; font-family: 宋体;"
>- (
void
)creatItemWithName:(
NSString
*)name phone:(
NSString
*)phone
{
// 创建对象
// 这个里面可以添加多个电话,email,地址等等。 感觉使用率不高,只提供了最常用的属性:姓名+电话,需要时可以自行扩展。
CNMutableContact * contact = [[CNMutableContact alloc]init];
contact.givenName = name?:@
"defaultname"
;
CNLabeledValue *phoneNumber = [CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberMobile value:[CNPhoneNumber phoneNumberWithStringValue:phone?:@
"10086"
]];
contact.phoneNumbers = @[phoneNumber];
// 把对象加到请求中
CNSaveRequest * saveRequest = [[CNSaveRequest alloc]init];
[saveRequest addContact:contact toContainerWithIdentifier:
nil
];
// 执行请求
CNContactStore * store = [[CNContactStore alloc]init];
[store executeSaveRequest:saveRequest error:
nil
];
}
</span>
|
五、我的demo
因为不同版本用的类和枚举都不一样,所以我要设置一个统一的,并且在我的manager中处理各个版本间的判断。 最后开放出来统一的api,只要引入头文件SXAddressBookManager.h 就可以使用这些通用接口了。
①检查当前状态,有两种api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
- (
void
)checkStatus1
{
SXAddressBookAuthStatus status = [[SXAddressBookManager manager]getAuthStatus];
if
(status == kSXAddressBookAuthStatusNotDetermined) {
[[SXAddressBookManager manager]askUserWithSuccess:^{
NSLog
(@
"点击同意"
);
} failure:^{
NSLog
(@
"点击拒绝"
);
}];
}
else
if
(status == kSXAddressBookAuthStatusAuthorized){
NSLog
(@
"已有权限"
);
}
else
{
NSLog
(@
"没有权限"
);
}
}
|
1
2
3
4
5
6
7
8
|
- (
void
)checkStatus2
{
[[SXAddressBookManager manager]checkStatusAndDoSomethingSuccess:^{
NSLog
(@
"已经有权限,做相关操作,可以做读取通讯录等操作"
);
} failure:^{
NSLog
(@
"未得到权限,做相关操作,可以做弹窗询问等操作"
);
}];
}
|
②弹出选择窗口,点击回调选中的信息
1
2
3
4
5
6
|
- (
void
)touchesBegan:(
NSSet
<UITouch *> *)touches withEvent:(UIEvent *)event
{
[[SXAddressBookManager manager]presentPageOnTarget:
self
chooseAction:^(SXPersonInfoEntity *person) {
NSLog
(@
"%@---%@"
,person.fullname,person.phoneNumber);
}];
}
|
③获得整个通讯录信息
1
|
self
.personEntityArray = [[SXAddressBookManager manager]getPersonInfoArray];
|
④往通讯录写入一条信息
1
|
[[SXAddressBookManager manager]creatItemWithName:@
"雷克萨斯-北京咨询电话"
phone:@
"010-88657869"
];
|
demo的地址是
https://github.com/dsxNiubility/SXEasyAddressBook
这里写了我说的那三点常用,如果以后有一些刚需,会不断补充。