首页 > 技术文章 > iOS开发——高级篇——通讯录

chglog 2015-10-09 01:12 原文

一、简介


1、如何访问用户的通讯录
1)在iOS9之前
有2个框架可以访问用户的通讯录
AddressBookUI.framework
提供了联系人列表界面、联系人详情界面、添加联系人界面等
一般用于选择联系人

AddressBook.framework
纯C语言的API,仅仅是获得联系人数据
没有提供UI界面展示,需要自己搭建联系人展示界面
里面的数据类型大部分基于Core Foundation框架,使用起来极其蛋疼

从iOS6开始,需要得到用户的授权才能访问通讯录,因此在使用之前,需要检查用户是否已经授权
获得通讯录的授权状态:ABAddressBookGetAuthorizationStatus()

2)在iOS9之后
也有2个框架可以访问用户的通讯录
ContactsUI.framework
提供了联系人列表界面、联系人详情界面、添加联系人界面等
一般用于选择联系人

Contacts.framework
没有提供UI界面展示,需要自己搭建联系人展示界面


2、授权状态
kABAuthorizationStatusNotDetermined
用户还没有决定是否授权你的程序进行访问

kABAuthorizationStatusRestricted
iOS设备上一些许可配置阻止程序与通讯录数据库进行交互

kABAuthorizationStatusDenied
用户明确的拒绝了你的程序对通讯录的访问

kABAuthorizationStatusAuthorized
用户已经授权给你的程序对通讯录进行访问

 

3、申请访问通讯录

// 实例化通讯录对象
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
    if (granted) {
        NSLog(@"授权成功!");
    } else {
        NSLog(@"授权失败!");
    }
});
CFRelease(addressBook);

提示:申请通讯录访问授权的代码,通常放在AppDelegate中

 

二、访问联系人信息


1、联系人属性定义
所有的属性常量值都定义在了ABPerson.h头文件中
联系人属性包括以下类型:
简单属性:姓、名等
多重属性:电话号码、电子邮件等
组合属性:地址等

注意:使用ABRecordCopyValue可以从一条Person记录中获取到对应的记录,但是后续处理则需要根据记录的具体类型加以区分


2、简单属性
一个联系人就是一个ABRecordRef,每个联系人都有自己的属性,比如名字、电话、邮件等
使用ABRecordCopyValue函数可以从ABRecordRef中获得联系人的简单属性(例如:一个字符串)
ABRecordCopyValue函数接收2个参数
第1个参数是ABRecordRef实例
第2个参数是属性关键字,定义在ABPerson.h中
ABPersonCopyLocalizedPropertyName函数可以根据指定的关键字获取对应的标签文本


3、获得所有的联系人数据

// 获取所有联系人记录
CFArrayRef array = ABAddressBookCopyArrayOfAllPeople(addressBook);
NSInteger count = CFArrayGetCount(array);

for (NSInteger i = 0; i < count; ++i) {
    // 取出一条记录
    ABRecordRef person = CFArrayGetValueAtIndex(array, i);
    
    // 取出个人记录中的详细信息
          //
    CFStringRef firstNameLabel = ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty);
    CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
    CFStringRef lastNameLabel = ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty);
    //
    CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
    
    NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName);
}


4、CoreFoundation 与 Foundation之间的桥接

// 1. 获取通讯录引用
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);
// 2. 获取所有联系人记录
NSArray *array = (__bridge NSArray *)(ABAddressBookCopyArrayOfAllPeople(addressBook));
for (NSInteger i = 0; i < array.count; i++) {
    // 取出一条记录
        ABRecordRef person = (__bridge ABRecordRef)(array[i]);
    // 取出个人记录中的详细信息
        NSString *firstNameLabel = (__bridge NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty));
    NSString *firstName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonFirstNameProperty));
    NSString *lastNameLabel = (__bridge NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty));
    NSString *lastName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonLastNameProperty));
    NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName);
}
CFRelease(addressBook);

 

5、多重属性
联系人的有些属性值就没这么简单,一个属性可能会包含多个值
比如邮箱,分为工作邮箱、住宅邮箱、其他邮箱等
比如电话,分为工作电话、住宅电话、其他电话等
如果是复杂属性,那么ABRecordCopyValue函数返回的就是ABMultiValueRef类型的数据,例如邮箱或者电话

// 取电话号码
ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
// 取记录数量
NSInteger phoneCount = ABMultiValueGetCount(phones);
// 遍历所有的电话号码
for (NSInteger i = 0; i < phoneCount; i++) {
…

 

6、获取复杂属性的方法

// 电话标签
CFStringRef phoneLabel = ABMultiValueCopyLabelAtIndex(phones, i);
// 本地化电话标签
CFStringRef phoneLocalLabel = ABAddressBookCopyLocalizedLabel(phoneLabel);
// 电话号码
CFStringRef phoneNumber = ABMultiValueCopyValueAtIndex(phones, i);

 

三、添加修改操作


1、添加联系人的步骤
通过ABPersonCreate函数创建一个新的联系人(返回ABRecordRef)
通过ABRecordSetValue函数设置联系人的属性
通过ABAddressBookAddRecord函数将联系人添加到通讯录数据库中
通过ABAddressBookSave函数保存刚才所作的修改

可以通过ABAddressBookHasUnsavedChanges函数判断是否有未保存的修改
当决定是否更改通讯录数据库后,你可以分别使用 AbAddressBookSave 或 ABAddressBookRevert 方式来保存或放弃更改


2、添加群组的步骤
添加群组的步骤大体和添加联系人一致
通过ABPersonCreate函数创建一个新的组(返回ABRecordRef)
通过ABRecordSetValue函数设置组名
通过ABAddressBookAddRecord函数将组添加到通讯录数据库中
通过ABAddressBookSave函数保存刚才所作的修改


3、操作联系人的头像
想操作联系人的头像,有以下函数
BPersonHasImageData
判断通讯录中的联系人是否有图片

ABPersonCopyImageData
取得图片数据(假如有的话)

ABPersonSetImageData
设置联系人的图片数据

 

四、实战演练


1、iOS9之前有界面

#import <AddressBookUI/AddressBookUI.h>
// 1.创建一个选择联系人的控制器
    ABPeoplePickerNavigationController *ppnc = [[ABPeoplePickerNavigationController alloc] init];
    
    // 2.设置代理
    ppnc.peoplePickerDelegate = self;//<ABPeoplePickerNavigationControllerDelegate>
    
    // 3.弹出控制器
    [self presentViewController:ppnc animated:YES completion:nil];
    
    
    
    #pragma mark - 实现ABPeoplePickerNavigationController的代理方法
/**
 *  当用户选择某一个联系人的时候会执行该方法(如果实现了该方法,那么一定不会执行下面的代理方法)
 *
 *  @param person       选中的联系人
 */
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person
{
    // 1.获取用户的姓名
    CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
    CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
    
    // 将CoreFoundation框架的对象转成Foundation框架对象,那么可以通过桥接的方式
    // 如果是CoreFoundation框架中的对象,如果是通过copy或者create或者retain,必须对应有一个release
    /*
     __bridge type: 通过该桥接方式,那么CoreFoundation对应的对象需要手动来释放,Foundation框架的对象如果是在ARC环境下面,则不需手动释放
     __bridge_transfer type: 通过该桥接方式,那么CoreFoundation对应的对象表示已经交给Foundation对象进行管理,如果是在ARC环境下面,不需要释放任何一个对象
     */
    NSString *lastname = (__bridge NSString *)(lastName);
    NSString *firstname = (__bridge_transfer NSString *)(firstName);
    NSLog(@"%@ %@", lastname, firstname);
    
    // 2.获取电话号码
    
    // 3.释放对象
    CFRelease(lastName);
}

/**
 *  当用户选择某一个联系人的某一个属性的时候会执行该方法
 *
 *  @param person       选中的联系人
 *  @param property     选中的联系人的属性
 *  @param identifier   每一个属性都有一个对应的表示
 */
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
    NSLog(@"%s", __func__);
}

/**
 *  当点击取消按钮时,会执行该方法
 *
 */
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker
{
    NSLog(@"%s", __func__);
}

 

2、iOS9之前无界面

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // 1.获取授权状态
    ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
    
    // 2.判断授权状态
    if (status == kABAuthorizationStatusNotDetermined) {
        // 3.请求授权
        // 3.1.创建通信录对象
        ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
        
        // 3.2.请求授权
        ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { // 当用户决定是否授权的时候会执行该block
            if (granted) { // 授权成功
                NSLog(@"可以访问通信录");
            } else { // 授权失败
                NSLog(@"不可以访问通信录");
            }
        });
        
        // 3.3.释放不再使用的对象
        CFRelease(addressBook);
    }
    
    return YES;
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 1.判断授权状态
    ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
    if (status != kABAuthorizationStatusAuthorized) return;
    
    // 2.获取联系人
    // 2.1.创建通信录对象
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
    
    // 2.2.获取所有的联系人
    CFArrayRef peopleArray = ABAddressBookCopyArrayOfAllPeople(addressBook);
    
    // 2.3.遍历所有的联系人
    CFIndex peopleCount = CFArrayGetCount(peopleArray);
    for (int i = 0; i < peopleCount; i++) {
        
        // 3.获取一条记录
        ABRecordRef person = CFArrayGetValueAtIndex(peopleArray, i);
        
        // 3.1.获取联系人的姓名
        NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
        NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
        NSLog(@"%@ %@", firstName, lastName);
        
        // 3.2.获取电话号码
        // 3.2.1.获取所有的电话
        ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
        // 3.3.2.遍历所有的电话号码
        CFIndex phoneCount = ABMultiValueGetCount(phones);
        for (int i = 0; i < phoneCount; i++) {
            NSString *phoneLabel = (__bridge_transfer NSString *)ABMultiValueCopyLabelAtIndex(phones, i);
            NSString *phoneValue = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phones, i);
            NSLog(@"%@ %@", phoneLabel, phoneValue);
        }
    }
}

 


3、三方框架RHAddressBook

#import <AddressBook/AddressBook.h>
#import <RHAddressBook/AddressBook.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // 1.获取授权状态
    RHAuthorizationStatus status = [RHAddressBook authorizationStatus];
    
    // 2.判断授权状态
    if (status == RHAuthorizationStatusNotDetermined) {
        // 3.请求授权
        // 3.1.创建通信录对象
        RHAddressBook *addressBook = [[RHAddressBook alloc] init];
        
        // 3.2.请求授权
        [addressBook requestAuthorizationWithCompletion:^(bool granted, NSError *error) {
            if (granted) {
                NSLog(@"授权成功");
            } else {
                NSLog(@"授权失败");
            }
        }];
    }
    
    return YES;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 1.获取授权状态
    RHAuthorizationStatus status = [RHAddressBook authorizationStatus];
    
    // 2.如果是已经授权,才能获取联系人
    if (status != RHAuthorizationStatusAuthorized) return;
    
    // 3.创建通信录对象
    RHAddressBook *addressBook = [[RHAddressBook alloc] init];
    
    // 4.获取所有的联系人
    NSArray *peopleArray = addressBook.people;
    
    // 5.遍历所有的记录
    for (RHPerson *person in peopleArray) {
        // 5.1.获取用户的姓名
        NSString *firstname = person.firstName;
        NSString *lastname = person.lastName;
        NSLog(@"%@ %@", firstname, lastname);
        
        // 5.2.获取电话号码
        RHMultiValue *phones = person.phoneNumbers;
        
        // 5.3.遍历所有的电话号码
        for (int i = 0; i < phones.count; i++) {
            NSString *phoneLabel = [phones labelAtIndex:i];
            NSString *phoneValue = [phones valueAtIndex:i];
            NSLog(@"%@ %@", phoneLabel, phoneValue);
        }
    }
}

 

4、iOS9之后有界面

#import <ContactsUI/ContactsUI.h>
<CNContactPickerDelegate>
    // 1.创建选择联系人的界面
    CNContactPickerViewController *cpvc = [[CNContactPickerViewController alloc] init];
    
    // 2.设置代理
    cpvc.delegate = self;
    
    // 3.弹出控制器
    [self presentViewController:cpvc animated:YES completion:nil];
    
#pragma mark - 实现CNContactPickerViewController的代理方法
/**
 *  当用户选中某一个联系人的时候会执行该方法
 *
 *  @param contact 选中的联系人
 */
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact
{
    // 1.获取联系人的姓名
    NSString *lastname = contact.familyName;
    NSString *firstname = contact.givenName;
    NSLog(@"%@ %@", lastname, firstname);
    
    // 2.获取电话号码
    for (CNLabeledValue *labelValue in contact.phoneNumbers) {
        // 3.获取电话的label/value
        NSString *phoneLabel = labelValue.label;
        CNPhoneNumber *phoneNumber = labelValue.value;
        NSString *phoneValue = phoneNumber.stringValue;
        NSLog(@"%@ %@", phoneLabel, phoneValue);
    }
}

/**
 *  当用户选中某一个联系人的某一个属性时候会执行该方法
 *
 *  @param contactProperty 选中的属性
 */
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty
{
    NSLog(@"%s", __func__);
}

 

5、iOS9之后无界面

#import <Contacts/Contacts.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // 1.获取授权状态
    CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
    
    // 2.判断授权状态,如果是未决定请求授权
    if (status == CNAuthorizationStatusNotDetermined) {
        // 3.请求授权
        // 3.1.创建CNContactStore对象
        CNContactStore *store = [[CNContactStore alloc] init];
        
        // 3.2.请求授权
        [store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (error) {
                NSLog(@"%@", error);
                return;
            }
            
            if (granted) {
                NSLog(@"授权成功");
            } else {
                NSLog(@"授权失败");
            }
        }];
    }
    
    return YES;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 1.判断授权状态
    CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
    if (status != CNAuthorizationStatusAuthorized) return;
    
    // 2.创建通信录对象
    CNContactStore *store = [[CNContactStore alloc] init];
    
    // 3.请求所有的联系人
    // 3.1.创建联系人请求对象,并且传入keys:你准备获取的信息(姓familyName名givenName 电话号码:phones)
    NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
    CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
    
    // 3.2.请求所有的联系人
    NSError *error = nil;
    [store enumerateContactsWithFetchRequest:request error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) { // 当遍历到一条记录就会执行该block
        // 4.获取联系人
        // 4.1.获取姓名
        NSString *firstName = contact.givenName;
        NSString *lastName = contact.familyName;
        NSLog(@"%@ %@", firstName, lastName);
        
        // 4.2.获取电话号码
        NSArray *phones = contact.phoneNumbers;
        for (CNLabeledValue *labelValue in phones) {
            NSString *phoneLabel = labelValue.label;
            CNPhoneNumber *phoneNumber = labelValue.value;
            NSString *phoneValue = phoneNumber.stringValue;
            NSLog(@"%@ %@", phoneLabel, phoneValue);
        }
    }];
}

 

推荐阅读