iOS 通讯录相关序一、介绍二、弹出系统通讯录程序三、代理方法四、不需要弹出联系人控制器就可以获取联系人信息的方法五、iOS 9 新出的点击通讯录的获取信息的办法

在简书写了一个月的blog,开始只是简单的将自己之前的笔记进行CCVV模式(command+c/v),渐渐地已经摸索出更多的套路,比如多写些demo,多加些配图,较多的知识点用思维导图做索引,markdown的语法也更加娴熟,每次整理都重新复习了下之前的知识点,也尽量让自己的思路让别人理解,所以觉得写blog还是一个比较好的习惯的,也感谢简书上这么多朋友关注,也是我继续写下去的动力,大家一起努力成为大神!


一、介绍

1.在iOS中,有2个框架可以访问用户的通讯录:

  • AddressBookUI.framework 提供了联系人列表界面、联系人详情界面、添加联系人界面等 一般用于选择联系人
  • AddressBook.framework 纯C语言的API,仅仅是获得联系人数据,没有提供UI界面展示,需要自己搭建联系人展示界面,里面的数据类型大部分基于Core Foundation框架,使用起来极其蛋疼

2.逻辑结构

逻辑结构

3.授权相关: (1)从iOS6开始,必须得到用户授权访问通讯录才能在AppStore上架(即使不授权也有时候可以访问通讯录) (2)申请通讯录访问授权的代码,通常放在AppDelegate中~! (3)获得通讯录的授权状态函数:ABAddressBookGetAuthorizationStatus() 例子:获取授权状态

ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();

(4)用户授权状态有4种:

kABAuthorizationStatusNotDetermined  用户未选择,用户还没有决定是否授权你的程序进行访问
kABAuthorizationStatusRestricted iOS设备上一些许可配置阻止程序与通讯录数据库进行交互
kABAuthorizationStatusDenied  用户明确的拒绝了你的程序对通讯录的访问
kABAuthorizationStatusAuthorized  用户已经授权给你的程序对通讯录进行访问

例子:在 AppDelegate的 didFinishLaunchingWithOptions方法中进行授权

#import <AddressBook/AddressBook.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //1. 获取授权状态
    ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
    //2. 创建 AddrssBook
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
    //3. 没有授权时就授权
    if (status == kABAuthorizationStatusNotDetermined) {
        ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
            //3.1 判断是否出错
            if (error) {
                return;
            }
            //3.2 判断是否授权
            if (granted) {
                NSLog(@"已经授权");
                CFRelease(addressBook);
            } else {
                NSLog(@"没有授权");
            }
        });
    }
    CFRelease(addressBook);
    return YES;
}

二、弹出系统通讯录程序

比如充话费时弹出的联系人选择界面(iOS 8 之前的方法,注意版本适配) 头文件:#import <AddressBookUI/AddressBookUI.h>

例子:点击弹出联系人控制器界面

弹出联系人控制器界面

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //1. 创建联系人选择控制器
    ABPeoplePickerNavigationController *picker = [ABPeoplePickerNavigationController new];
    //2. 设置代理,注意不是 Delegate
    picker.peoplePickerDelegate = self;
    //3. 模态视图弹出
    [self presentViewController:picker animated:YES completion:nil];
}

三、代理方法

  • 有UI交互,即弹出联系人控制器才能获取联系人信息的方法

iOS7和iOS8适配

  • iOS 8之后如不想自动dismiss可以在弹出联系人控制器方法中加入如下代码:
if([[UIDevice currentDevice].systemVersion floatValue] >= 8.0){
    picker.predicateForSelectionOfPerson = [NSPredicate predicateWithValue:false];
}
  • 设置代理可以获取点击后通讯录里的值,注意代理名不是deleagte
@property(nonatomic,assign,nullable) id<ABPeoplePickerNavigationControllerDelegate> peoplePickerDelegate;
以下1、2两个代理方法,如果同时实现, 只会运行第一个方法!

1.第一个代理方法:选中某个联系人时调用(iOS8之后实现后再无法调用第2个的方法)

- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person NS_AVAILABLE_IOS(8_0);

参数 (ABRecordRef)person 介绍:

  1. 一个联系人就是一个ABRecordRef对象,就相当于一条记录,每个联系人都有自己的属性,比如名字、电话、邮件等,使用ABRecordCopyValue函数可以从ABRecordRef中获得联系人的简单属性(参见下面简单属性的介绍)
  2. ABRecordCopyValue 从记录中取值函数:
CFTypeRef ABRecordCopyValue(ABRecordRef record, ABPropertyID property)

有2个参数:

  • 第1个参数是ABRecordRef实例
  • 第2个参数ABPropertyID是属性关键字,定义在ABPerson.h中,下面有介绍
  1. 注意:使用ABRecordCopyValue可以从一条Person记录中获取到对应的值,但是后续还需要根据值的具体类型再加以处理(比如简单属性取的值不需要再处理,多重属性的值还需要通过其它函数再取值)

ABPropertyID联系人属性介绍

ABPropertyID 就是联系人的属性,所有的属性常量值都定义在了ABPerson.h头文件中 联系人属性包括以下类型: (1)简单属性:姓 kABPersonLastNameProperty,名 kABPersonFirstNameProperty 等 (2)组合属性:地址等 kABPersonAddressProperty (3)多重属性:电话号码 kABPersonPhoneProperty 、电子邮件 kABPersonEmailProperty 等 联系人的有些属性值就没这么简单,一个属性可能会包含多个值,比如邮箱,分为工作邮箱、住宅邮箱、其他邮箱等,比如电话,分为工作电话、住宅电话、其他电话等

如果是多重属性,那么ABRecordCopyValue函数返回的就是ABMultiValueRef类型的数据,例如邮箱或者电话
// 取电话号码
ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
// 取记录数量
NSInteger phoneCount = ABMultiValueGetCount(phones);
// 遍历所有的电话号码
for (NSInteger i = 0; i < phoneCount; i++) {...}
获取多重属性的方法
// 电话标签
CFStringRef phoneLabel = ABMultiValueCopyLabelAtIndex(phones, i);
// 本地化电话标签
CFStringRef phoneLocalLabel = ABAddressBookCopyLocalizedLabel(phoneLabel);
// 电话号码
CFStringRef phoneNumber = ABMultiValueCopyValueAtIndex(phones, i);

4.ABPersonCopyLocalizedPropertyName(ABPropertyID property) 函数可以根据指定的关键字获取对应的标签文本(关于标签,下面案例2有介绍)

案例:

下面案例都是在该代理方法中使用,如果打印不出,尝试在AppDelegate中进行授权~!

  • 例子1:从记录中取姓,并转换为NSString类型
CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
NSString *lastNameStr = (__bridge NSString *)(lastName);
CFRelease(lastName); // 使用__bridge type 方法记得释放!
  • 例子2:获取电话号码, 电话返回的是多数据类型(可以获取到标签和电话号等信息) 如:住宅就是标签,下面是电话号

标签和值的概念 如:打印该方式获取到的联系人电话的标签和电话号码

打印查看标签和值

// 获取电话,电话是多数据类型
ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
// 获取电话的个数
CFIndex count = ABMultiValueGetCount(phones);
// 遍历联系人,取出每个电话标签和电话号码,CF框架必须用for i循环
for (CFIndex i = 0 ; i < count; i++) {
    // 获取联系电话的标签,使用__bridge_transfer方法不用释放
    NSString *label = (__bridge_transfer NSString *)ABMultiValueCopyLabelAtIndex(phones,  i);
    NSLog(@"label: %@",label);
    // 获取联系电话,使用CFBridgingRelease方法和上面功能一样也不需要释放
    NSString *value = CFBridgingRelease(ABMultiValueCopyValueAtIndex(phones,  i));
    NSLog(@"value: %@",value);
}
//phones 对象需要被释放
CFRelease(phones);
  • 例子3:获取通讯录中所有联系人信息
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person {
    // 1. 获取系统通讯录应用
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);
    // 2. 获取所有联系人记录
    NSArray *array = (__bridge_transfer NSArray *)(ABAddressBookCopyArrayOfAllPeople(addressBook));
    for (NSInteger i = 0; i < array.count; i++) {
        // 取出一条记录
        ABRecordRef person = (__bridge ABRecordRef)(array[i]);
        // 取出个人记录中的详细信息
        NSString *firstNameLabel = (__bridge_transfer NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty));
        NSString *firstName = (__bridge_transfer NSString *)(ABRecordCopyValue(person, kABPersonFirstNameProperty));
        NSString *lastNameLabel =(__bridge_transfer NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty));
        NSString *lastName =(__bridge_transfer NSString *)(ABRecordCopyValue(person, kABPersonLastNameProperty));
        NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName);
    }
    CFRelease(addressBook);
}
关于Core Foundation 和 NSFundation 桥接的问题:

注意:无论CF对象是否被引用,只要使用CF函数,就会产生CF对象留在内存中,如:ABMultiValueCopyValueAtIndex(phones, i)这一句,建议点击静态内存分析工具,可发现没有被释放的CF对象。

桥接有三种方式:

  1. (__bridge type)(expression) : 只是让NSFoundation框架暂时使用CF框架对象,注意需要手动释放 Core Foundation 对象,用CFRelease( )函数。
  2. (__bridge_transfer type)(expression) / CFBridgingRelease(expression) : CF框架移交对象的管理权给NSFoundation框架,不需要手动释放对象
  3. 前两种是将CF对象转NSFoundation,最后一个是NSFoundation转 CF对象,不常用 (__bridge_retained <#CF type#>)(<#expression#>)
其它知识点

1、添加联系人的步骤 通过ABPersonCreate函数创建一个新的联系人(返回ABRecordRef) 通过ABRecordSetValue函数设置联系人的属性 通过ABAddressBookAddRecord函数将联系人添加到通讯录数据库中 通过ABAddressBookSave函数保存刚才所作的修改 可以通过ABAddressBookHasUnsavedChanges函数判断是否有未保存的修改 当决定是否更改通讯录数据库后,你可以分别使用 AbAddressBookSave 或 ABAddressBookRevert 方式来保存或放弃更改

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

3、 想操作联系人的头像,有以下函数 BPersonHasImageData 判断通讯录中的联系人是否有图片 ABPersonCopyImageData 取得图片数据(假如有的话) ABPersonSetImageData 设置联系人的图片数据


2.第二个代理方法,选中联系人某个属性(详细的信息,如电话号码)的时候调用,注意和上面的方法只能实现一个,如同时实现无法跳转到联系人详情页面,优先上面的方法

该方法可以获取具体的哪个电话号码,例如使用充值话费时不能使用上面方法,因为无法确定具体充值哪个号码

- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier NS_AVAILABLE_IOS(8_0);

更详细的属性,联系人详情界面

例子:获取用户点击的,确定的某个电话号码或联系人信息

#pragma mark 选中联系人的某个属性的时候调用
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier {
    // 获取该联系人多重属性--电话号
    ABMutableMultiValueRef phoneMulti = ABRecordCopyValue(person, kABPersonPhoneProperty);

    // 获取该联系人的名字,简单属性,只需ABRecordCopyValue取一次值
    ABMutableMultiValueRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
    NSString *name = (__bridge NSString *)(firstName);
    // 获取点击的联系人的电话
    NSLog(@"联系人名字 : %@",name);

    // 点击某个联系人电话后dismiss联系人控制器,并回调点击的数据
    [self dismissViewControllerAnimated:YES completion:^{
        // 从多重属性——电话号中取值,参数2是取点击的索引
        NSString *aPhone =  (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phoneMulti, ABMultiValueGetIndexForIdentifier(phoneMulti,identifier)) ;
        // 获取点击的联系人的电话,也可以取标签等
        NSLog(@"联系人电话 : %@",aPhone);
        // 去掉电话号中的 "-"
        aPhone = [aPhone stringByReplacingOccurrencesOfString:@"-" withString:@"" ];
        NSLog(@"去掉-号 : %@",aPhone);
    }];
}

3.第三个代理方法:取消选中联系人的时候调用

注意:在iOS 7 下必须实现此方法,否则会崩溃!

- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker;

4.下面两个是iOS7选择联系人代理方法,iOS 8之前才会调用,适配iOS 7时实现,适配iOS 8之后使用上面两个方法

1.返回YES,则会跳转到联系人详情页面,如果返回NO必须手动实现控制器dismiss方法

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person NS_DEPRECATED_IOS(2_0, 8_0);

2.返回NO不会执行默认的操作,如:打电话,必须手动实现控制器dismiss方法

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier NS_DEPRECATED_IOS(2_0, 8_0);

四、不需要弹出联系人控制器就可以获取联系人信息的方法

#pragma mark - 点击屏幕获取所有联系人信息,记得授权
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //1. 判断是否授权成功, 授权成功才能获取数据
    if ( ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
        //2. 创建通讯录
        ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
        //3. 获取所有联系人
        CFArrayRef peosons = ABAddressBookCopyArrayOfAllPeople(addressBook);
        //4. 遍历所有联系人来获取数据(姓名和电话)
        CFIndex count = CFArrayGetCount(peosons);
        for (CFIndex i = 0 ; i < count; i++) {
            //5. 获取单个联系人
            ABRecordRef person = CFArrayGetValueAtIndex(peosons, i);
            //6. 获取姓名
            NSString *lastName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonLastNameProperty));
            NSString *firstName  = CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
            NSLog(@"lastName: %@, firstName: %@", lastName, firstName);
            //7. 获取电话
            ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
            //7.1 获取电话的count数
            CFIndex phoneCount = ABMultiValueGetCount(phones);
            //7.2 遍历所有电话号码
            for (CFIndex i = 0; i < phoneCount; i++) {
                NSString *label = CFBridgingRelease(ABMultiValueCopyLabelAtIndex(phones, i));
                NSString *value = CFBridgingRelease(ABMultiValueCopyValueAtIndex(phones, i));
                // 打印标签和电话号
                NSLog(@"label: %@, value: %@",label, value);
            }
            NSLog(@"\\n\\n");
            //8.1 释放 CF 对象
            CFRelease(phones);
        }
        //8.1 释放 CF 对象
        CFRelease(peosons);
        CFRelease(addressBook);
    }
}

五、iOS 9 新出的点击通讯录的获取信息的办法

有UI交互,即需要点击联系人控制器,代理属性为delegate 头文件:#import <ContactsUI/ContactsUI.h>

#pragma mark - 先弹出联系人控制器
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 1. 创建控制器
    CNContactPickerViewController * picker = [CNContactPickerViewController new];
    // 2. 设置代理
    picker.delegate = self;
    // 3. 设置相关属性,谓词筛选email地址是@mac.com的联系人
    picker.predicateForSelectionOfProperty = [NSPredicate predicateWithFormat:@"(key == 'emailAddresses') AND (value LIKE '*@mac.com')"];
        / / 谓词筛选email地址数等于1的联系人
    picker.predicateForSelectionOfContact = [NSPredicate predicateWithFormat:@"emailAddresses.@count == 1"];
    // 4. 弹出
    [self presentViewController: picker  animated:YES completion:nil];
}
#pragma mark - 取消选中联系人的时候调用,点击右上角的cancel时候触发,而不是picker的所有dismiss动作中都会触发。在多选模式下,cancel在done的左侧。
- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker {

}
其他四个代理方法只要实现其中一个就行了。分别为单选和多选两组,都实现的时候,多选优先执行,单选不执行。特别要注意的是predicateForEnablingContact,predicateForSelectionOfContact,predicateForSelectionOfProperty这三组属性会影响它们的动作。predicateForEnablingContact返回YES的联系人才是可交互的,默认联系人都是可交互的。

多选界面

#pragma mark - 选择联系人的时候调用 (如果predicateForSelectionOfContact属性没被设置或符合筛选条件,如不符合则不会触发该方法并进入联系人详情页)
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact {
    //1. 获取姓名 ,givenName == firstName
    NSLog(@"givenName: %@, familyName: %@", contact.givenName, contact.familyName);
    //2. 获取电话,泛型,会在数组遍历时帮很大的忙
    for (CNLabeledValue *labeledValue in contact.phoneNumbers) {
        NSLog(@"label: %@",labeledValue.label);
        CNPhoneNumber *phoneNumber = labeledValue.value;
        NSLog(@"phoneNumber: %@",phoneNumber.stringValue);
    }
}
#pragma mark - 实现了此方法, 就可以选择多个联系人,该方法在点击done按钮时触发,注意:该方法不受predicateForSelectionOfContact属性影响!
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContacts:(NSArray<CNContact *> *)contacts {
    for (CNContact *contact in contacts) {
        NSLog(@"givenName: %@, familyName: %@", contact.givenName, contact.familyName);
        //2. 获取电话,泛型,会在数组遍历是帮很大的忙
        for (CNLabeledValue *labeledValue in contact.phoneNumbers) {
            NSLog(@"label: %@",labeledValue.label);
            CNPhoneNumber *phoneNumber = labeledValue.value;
            NSLog(@"phoneNumber: %@",phoneNumber.stringValue);
        }
    }
}
#pragma mark - 点击某个联系人的某个属性(property)时触发并返回该联系人属性(contactProperty)。
只实现该方法时,可以进入到联系人详情页面(如果predicateForSelectionOfProperty属性没被设置或符合筛选条件,如不符合会触发默认操作,即打电话,发邮件等)。
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty {
}
#pragma mark - 只实现该方法时,停留在多选模式下的联系人列表页面(如果predicateForSelectionOfProperty属性没被设置或符合筛选条件,该联系人才能被选中),在点击done按钮的时候触发,返回的contactProperties中只包含选中的contactProperties,没选中的话返回空。
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperties:(NSArray<CNContactProperty *> *)contactProperties {
    // 循环打印出所有选中的联系人名字
    for (CNContactProperty *contactProperty in contactProperties) {
        NSLog(@"%@",contactProperty.contact.givenName);
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据和云

【性能优化】一个执行计划异常变更的案例(上)

作者简介: ? 刘晨,网名bisal,Oracle 10g/11g OCM,并国内首批Oracle YEP成员, 博客:blog.itpub.net/bisal...

27110
来自专栏分布式系统和大数据处理

数据库对象命名参考

编码规范是一个优秀程序员的必备素质,然而,有很多人非常注重程序中变量、方法、类的命名,却忽视了同样重要的数据库对象命名。这篇文章结合许多技术文章和资料,以及我自...

1202
来自专栏liuchengxu

用 Python 写一个 NoSQL 数据库

本文译自 What is a NoSQL Database? Learn By Writing One In Python.

1563
来自专栏前端儿

数据库初识--从MySQL 出发

要学Web 开发,也得先对数据库有所了解呀。数据库分门别类,多种多样,目前我选择了 MySQL 。

1312
来自专栏高性能服务器开发

数据库进阶5 Mysql 性能优化20个原则(3)

Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用 prepared statements 获得很多好处,无论...

1142
来自专栏Java3y

移动商城第五篇(用户模块)【用户登陆、回显用户、拦截器、收货地址】

移动商城【用户登陆、回显用户】 我们来实现用户登陆的功能: ? 当点击的时候,出来的是一个弹出框,我们想要切换成一个页面。 ? 找到对应的事件、切换成我们的页面...

5247
来自专栏一“技”之长

iOS开发之AddressBook框架详解

    首先,AddressBook框架是一个已经过时的框架,iOS9之后官方提供了Contacts框架来进行用户通讯录相关操作。尽管如此,AddressBoo...

2621
来自专栏Hadoop数据仓库

HAWQ技术解析(十一) —— 数据管理

一、基本操作 1. INSERT         在常用的增删改查数据库操作中,HAWQ仅支持INSERT和SELECT两种,不支持UPDATE和DELETE,...

2815
来自专栏玩转JavaEE

MongoDB中各种类型的索引

上篇文章中我们介绍了MongoDB中索引的简单操作,创建、查看、删除等基本操作,不过上文我们只介绍了一种类型的索引,本文我们来看看其他类型的索引。 ---- _...

2977
来自专栏北京马哥教育

用 Python 写一个 NoSQL 数据库

本文译自 What is a NoSQL Database? Learn By Writing One In Python. 完整的示例代码已经放到了 Git...

3179

扫码关注云+社区

领取腾讯云代金券