iOS框架—使用地址簿


从iOS6起,新增了限制,即访问通讯录必须经过用户的允许。

一、为何支持地址簿很重要:

当开发iOS软件的时候,就与用户的移动生活建立了联系。用户走到哪里都带着移动设备,可以说移动设备与用户的个人生活(从日历到个人相册)休戚相关。在这种移动生活中,通讯录占据着重要的位置。应用可以使用联系人数据库确定用户是否有朋友注册了相关服务:分析联系人的电子邮件地址或电话号码,并将联系人自动添加为好友。应用还可以使用联系人列表自动填写邮件地址或电话号码,或让用户通过蓝牙与朋友分享联系人信息。应用需要访问用户通讯录的原因不胜枚举。

注意:

除非有充分的理由,否则不要访问联系人数据库,这很重要,因为没有什么比泄露隐私更能让用户毅然决然地放弃使用您的应用了。

二、使用地址簿

想要使用地址簿框架,需要先将两个有关地址薄的框架导入到项目中:AddressBookUI.frameworkAddressBook.framework。其中前者提供了选择、编辑和显示联系人的图形用户界面,而后者是让您能够与联系人数据交互。然后在相关的类中导入头文件,如下所示:

#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>

首先,我在项目中创建了两个类的属性:addressBookaddressBookEntryArray

@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);

这里获取了联系人中的firstNamelastName
ABRecordRef的所有单值属性如下表所示:

属性截图-1

四、从地址薄读取多值数据

当从地址薄中读取多值数据,例如:电话号码、电子邮件地址和街道地址这些数据时,需要用到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去获得对应的数据。
地址的组成部分由下表所示:

属性截图-2

五、地址标签的使用

在通过多值获取到电话号码后,使用的是电话号码的索引号。虽然电话号码索引对开发人员很有用,但是对用户来说毫无意义,因此这里使用地址标签获取到联系人使用的标签。要获取多值引用的标签,首先需要调用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];

当用户保存联系人时,会调用一个委托方法。在这个方法中,确定返回的联系人对象有效后,会依次调用ABAddressBookAddRecordABAddressBookSave将联系人保存到地址薄。

- (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];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容