从iOS6起,新增了限制,即访问通讯录必须经过用户的允许。
一、为何支持地址簿很重要:
当开发iOS软件的时候,就与用户的移动生活建立了联系。用户走到哪里都带着移动设备,可以说移动设备与用户的个人生活(从日历到个人相册)休戚相关。在这种移动生活中,通讯录占据着重要的位置。应用可以使用联系人数据库确定用户是否有朋友注册了相关服务:分析联系人的电子邮件地址或电话号码,并将联系人自动添加为好友。应用还可以使用联系人列表自动填写邮件地址或电话号码,或让用户通过蓝牙与朋友分享联系人信息。应用需要访问用户通讯录的原因不胜枚举。
注意:
除非有充分的理由,否则不要访问联系人数据库,这很重要,因为没有什么比泄露隐私更能让用户毅然决然地放弃使用您的应用了。
二、使用地址簿
想要使用地址簿框架,需要先将两个有关地址薄的框架导入到项目中:AddressBookUI.framework和AddressBook.framework。其中前者提供了选择、编辑和显示联系人的图形用户界面,而后者是让您能够与联系人数据交互。然后在相关的类中导入头文件,如下所示:
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
首先,我在项目中创建了两个类的属性:addressBook
和addressBookEntryArray
@property (nonatomic, assign) ABAddressBookRef addressBook;
@property (nonatomic, strong) NSMutableArray *addressBookEntryArray;
addressBookEntryArray
是用来存储获取到的系统中的地址薄条目数组。
注意:
将地址薄复制到内存的开销极高,因此应最大限度地减少这种操作的次数。
在使用地址簿的时候,我们应该获取访问手机中地址薄的权限:
//申请访问权限
ABAddressBookRequestAccessWithCompletion(_addressBook, ^(bool greanted, CFErrorRef error) {
//greanted为YES是表示用户允许,否则为不允许
if (!greanted) {
NSLog(@"未取得权限");
}
});
接下来,创建一个地址薄的引用:
CFErrorRef error = NULL;
//创建通讯簿的引用
_addressBook = ABAddressBookCreateWithOptions(nil, &error);
在创建地址薄引用的时候现在最好用ABAddressBookCreateWithOptions
这个方法,原来的 ABAddressBookCreate
已经作废。
AB_EXTERN ABAddressBookRef ABAddressBookCreateWithOptions(CFDictionaryRef options, CFErrorRef* error) __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_6_0);
AB_EXTERN ABAddressBookRef ABAddressBookCreate(void) __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_NA,__MAC_NA,__IPHONE_2_0,__IPHONE_6_0);
由上面的代码可以看出, ABAddressBookCreate
支持的版本是iOS 2.0 到 iOS 6.0,在开发iOS 6.0 之后系统的时候应该使用 ABAddressBookCreateWithOptions
去创建地址薄的引用。
_addressBookEntryArray = (__bridge NSMutableArray *)ABAddressBookCopyArrayOfAllPeople(_addressBook);
创建了地址薄的引用后,就要将手机地址薄中的联系人拷贝到数组addressBookEntryArray中,这里,我拷贝了所有联系人的信息,其他的拷贝方式请自行查看API文档。在获取所有联系人之前需要考虑到地址薄为空的情形,这种情况下给用户一个良好的提示,让用户知道APP没有出错崩溃,这里我用了一个alert弹窗来简单的显示提示信息。
if(ABAddressBookGetPersonCount(_addressBook) == 0) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"联系人为空"
message:@""
delegate:nil
cancelButtonTitle:@"确定"
otherButtonTitles: nil];
[alertView show];
}
在读到地址薄中联系人信息后,这里将获得的数据展示在了一个tableviewcontroller
中。
三、从地址簿中读取数据
读入到addressBookEntryArray
数组中的每一个条目都是一个ABRecordRef
,单值数据可以用方法ABRecordCopyValue
方法读取:
ABRecordRef record = (__bridge ABRecordRef)([_addressBookEntryArray objectAtIndex:indexPath.row]);
CFStringRef firstName = ABRecordCopyValue(record, kABPersonFirstNameProperty);
CFStringRef lastName = ABRecordCopyValue(record, kABPersonLastNameProperty);
这里获取了联系人中的firstName
和lastName
。
ABRecordRef
的所有单值属性如下表所示:
四、从地址薄读取多值数据
当从地址薄中读取多值数据,例如:电话号码、电子邮件地址和街道地址这些数据时,需要用到ABMultiValueRef
。这里展示了如何操作地址薄中的地址(地址是一个比较特殊的多值数据,因为取出来的是一个字典类型的数据):
// 通讯录地址的使用
ABMultiValueRef addressMulti = ABRecordCopyValue(record, kABPersonAddressProperty);
NSString *address = nil;
NSString *street = nil;
NSString *city = nil;
NSString *state = nil;
NSString *zip = nil;
if (ABMultiValueGetCount(addressMulti) > 0) {
NSDictionary *addressDictionary = (__bridge NSDictionary *)(ABMultiValueCopyValueAtIndex(addressMulti, 0));
street = [addressDictionary objectForKey:(NSString *)kABPersonAddressStreetKey];
city = [addressDictionary objectForKey:(NSString *)kABPersonAddressCityKey];
state = [addressDictionary objectForKey:(NSString *)kABPersonAddressStateKey];
zip = [addressDictionary objectForKey:(NSString *)kABPersonAddressZIPKey];
}
address = [NSString stringWithFormat: @"%@ %@ %@ %@", street, city, state, zip];
从API中得知,地址是一个字典类型的值。首先将地址取出,然后在转换成字典,用API中对应的Key去获得对应的数据。
地址的组成部分由下表所示:
五、地址标签的使用
在通过多值获取到电话号码后,使用的是电话号码的索引号。虽然电话号码索引对开发人员很有用,但是对用户来说毫无意义,因此这里使用地址标签获取到联系人使用的标签。要获取多值引用的标签,首先需要调用ABMultiValueCopyLabelAtIndex
。调用这个函数时,使用的参数与获取多值对象的值时相同。这个函数返回一个未经本地化的字符串如:_$!<Mobile>!$_
。虽然这个字符串比索引号有用的多,但还是不太合适显示给用户。为获得用户能看明白的字符串,需要将返回的标签进行本地化。为此,可调用ABAddressBookCopyLocalizedLabel
,并将刚返回的CFStringRef
作为参数。代码中我获取了电话号码的标签,并输出到后台:
// 读取多个值的方法
ABMultiValueRef phoneNumbers = ABRecordCopyValue(record, kABPersonPhoneProperty);
if (ABMultiValueGetCount(phoneNumbers) > 0) {
for (int i = 0; i < ABMultiValueGetCount(phoneNumbers); i++) {
// 第二个参数是获取电话号码的类型,例如:手机,家庭电话等(地址簿标签)
NSLog(@"%@ [%@]", ABMultiValueCopyValueAtIndex(phoneNumbers, i), ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(phoneNumbers, i)));
}
}
六、联系人选择器
联系人选择器创建一个像系统内置的地址薄中的联系人选择器类似的一个界面。首先这个类需要遵守协议ABPeoplePickerNavigationControllerDelegate
,然后使用如下的代码创建一个选择控制器。
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentViewController:picker animated:YES completion:nil];
选择器创建完成后,这里有三个委托方法,在用户与联系人选择器交互时做出相应。第一个方法是对用户“取消”按钮做出响应:
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
[self dismissViewControllerAnimated:YES completion:nil];
}
第二个委托方法是告诉联系人选择器,您不想让用户选择联系人的具体属性,即用户无法通过联系人选择器查看到联系人的详细信息。当用户选择一个联系人后,联系人选择器就会自动dismiss
掉。
// 此方法选择联系人后,不能跳转到详细页面,即不能读取详细信息
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person {
NSLog(@"You have selected: %@", ABRecordCopyValue(person, kABPersonFirstNameProperty));
}
第三个委托方法实现后,用户可以查看联系人的详细信息。
// 可以获取联系人的详细信息
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
NSLog(@"Person: %@, Property: %i, Identifier: %i", person, property, identifier);
}
这里我们还可以定制联系人选择器进入详细页面后所能访问的内容,在创建联系人选择器后,加入下面的代码用户进入详细页面后只能查看联系人的电话号码:
picker.displayedProperties = [NSArray arrayWithObject: [NSNumber numberWithInt:kABPersonPhoneProperty]];
六、使用ABPersonViewController编辑和查看联系人
大多数的情况下,都使用内置的地址薄用户界面来显示或编辑联系人。在此代码中,将联系人信息显示在了一个tableview
中,选中cell
后将跳转到一个ABPersonViewController
页面进行联系人详细信息的查看。
ABPersonViewController *personViewController = [[ABPersonViewController alloc] init];
ABRecordRef record = (__bridge ABRecordRef)([_addressBookEntryArray objectAtIndex:indexPath.row]);
personViewController.personViewDelegate = self;
personViewController.displayedPerson = record;
personViewController.allowsEditing = YES;
personViewController.allowsActions = NO;
[self.navigationController pushViewController:personViewController animated:YES];
这里的allowsEditing
属性,是设置进入详细页面后,是否能够编辑联系人信息。
七、使用ABNewPersonViewController新建联系人
ABNewPersonViewController *newPersonController = [[ABNewPersonViewController alloc] init];
newPersonController.newPersonViewDelegate = self;
[self.navigationController pushViewController:newPersonController animated:YES];
当用户保存联系人时,会调用一个委托方法。在这个方法中,确定返回的联系人对象有效后,会依次调用ABAddressBookAddRecord
和ABAddressBookSave
将联系人保存到地址薄。
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonView didCompleteWithNewPerson:(ABRecordRef)person {
if (person) {
CFErrorRef error = NULL;
ABAddressBookAddRecord(_addressBook, person, &error);
ABAddressBookSave(_addressBook, &error);
if (error != NULL) {
NSLog(@"An error occurred");
}
}
_addressBookEntryArray = (__bridge NSMutableArray *)ABAddressBookCopyArrayOfAllPeople(_addressBook);
[self.tableView reloadData];
[self.navigationController popToRootViewControllerAnimated:YES];
}