尽管OC是一门面向对象的语言,但是在你做开发的时候你会发现,并不是所有你用的frameworks都是面向对象的。有些是用C写的,例如Address Book的API,接下来让我们去学习一下Address Book。

我们在我们的APP中可以Address Book API来读取或者修改用户联系人的信息(这和我们在手机通讯录上的效果是一样的)。

因为Address Book API是基于C语言的,它不是使用的对象,而且它也利用了一些其他的类型,在这里,你将会熟悉一下几个API:

  • ABRecordRef:它是一个联系记录,包括了所有的属性,例如手机,电话,电子邮件,姓,名等等。
  • ABAddresBookRef:它是所有用户联系人的集合,你可以对记录进行增加、修改和删除。
  • ABMutableMultiValueRef:它是ABMultiValueRef的可变类型(类似于NSDictionary的NSMutableDictionary),虽然它是方便的,但是它要求你设置ABRecordeRef属性的时候有多个实体,例如电话号码或者email.

既然读这个文章,那么就意味着你对iOS开发有一个基础的了解,而且熟悉C的基础语法。如果你没有满足刚才说的两个条件,可以先对iOS进行复习或者先了解C语言。

好了,让我们开始学习吧!

开始

首先,你可以先到这里下载这个界面程序,然后在这个基础上进行开发学习Address Book。(这个程序很简单,就是放了4个button,然后来了一个输出,用不同的tag标记不同按钮)

使用Address Book API,你需要导入头文件,导入方式如下:

@import AddressBook;

或者直接:

#import <AddressBook/AddressBook.h> 

在这个小Demo中,用户将可以点击任何一个图片,然后这个宠物联系人的信息就会存储到address Book里面。使用Address Book API,你可以联系到你的存储的朋友。

请求权限

在2012年,有一个争论:app是否可以复制用户的通讯录,然后将数据发送到自己的服务器。大众的响应肯定是不允许,就算是发送也要经过用户同意。所有Apple就诞生了一个新的特性:请求权限。防止用户在不知情的情况下自己的通讯录被APP盗取。

因此,现在如果你想使用Address Book,你首先要得到用户的允许。

让我们来尝试做一下,在ViewController.m中,添加如下代码到按钮点击事件:

- (IBAction)tapAction:(id)sender {
//iOS 8 and before
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted) {
//1
NSLog(@"Denied");
}else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
//2
NSLog(@"Authorized");
}else {
//3
NSLog(@"Not determined");
} // //iOS 9 and later
// CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
// if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {
// NSLog(@"Denied");
// }else if (status == CNAuthorizationStatusAuthorized ) {
// NSLog(@"Authorized");
// }else {
// NSLog(@"Not determined");
// }
}

让我们来分析一下:

  1. 这个检查是用来检测用户是否拒绝了你的app访问手机通讯录,或者是它是受限制的(比如家长控制).如果用户拒绝了或者限制了,那么你只能告诉用户没有权限对通讯录进行操作,其他的什么也无法做。
  2. 这个检查是看看用户是否已经允许你的APP访问用户的通讯录,如果允许了,你可以随意地修改或者对通讯录进行其他操作。
  3. 这个检查是看用户是否还没有确定你的APP具有访问通讯录权限。

输出结果如下:

2016-09-13 16:46:35.513 ABContractDemo[14369:395357] Not determined

和现实生活一样:你需要什么东西的时候,你需要询问。

因此,你需要请求用户获取访问权限,在3的地方写如下代码:(这里就不在介绍iOS9)

        ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) {
if (granted) {
NSLog(@"Just authorized");
}else {
NSLog(@"Just deieny");
}
});

这里面第一个参数是ABAddressBookRef,你使用ABAddressBookCreateWithOptions(NULL,nil)。第二个参数是一个block:一旦用户点击了授权按钮,便会调用里面的东西。

这次运行的结果就是:

当你点击了Don't Allow,就表明iDenied了,如果ok就表示你允许了APP访问通讯录。

创建记录

现在,让我们开始去创建通讯录记录。我们现在清空按钮点击事件,然后重写它。在重写的这个方法里面,你需要创建一个ABRecordRef,他包括了宠物的属性,检查一下通讯录确保不存在你添加的联系人,如果宠物不在通讯录,就把他加入到通讯录。

在tapAction:(id)sender方法里面写入:

  NSString *petFirstName;
NSString *petLastName;
NSString *petphoneNumber;
NSData *petImageData;
if (sender.tag == ) {
petFirstName = @"Cheesy";
petLastName = @"Cat";
petphoneNumber = @"";
petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Cheesy.jpg"], 0.7f);
}else if (sender.tag == ) {
petFirstName = @"Freckles";
petLastName = @"Dog";
petphoneNumber = @"";
petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Freckles.jpg"], 0.7f);
}else if (sender.tag == ) {
petFirstName = @"Maxi";
petLastName = @"Dog";
petphoneNumber = @"";
petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Maxi.jpg"], 0.7f);
}else if(sender.tag == ) {
petFirstName = @"Shippo";
petLastName = @"Dog";
petphoneNumber = @"";
petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Shippo.jpg"], 0.7f);
}

通过点击不同的按钮,可以确定点击的是哪个宠物。接下来,写如下代码

    ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, nil);
ABRecordRef pet = ABPersonCreate();

第一行是创建一个ABAddressBookRef,它稍后用户将pet加到用户的通讯录中。第二行是为你的创建了一个空的记录,用来填充宠物的信息。

接下来,设置宠物的姓和名,代码如下:

 ABRecordSetValue(pet, kABPersonFirstNameProperty, (__bridge CFStringRef)(petFirstName), nil);
ABRecordSetValue(pet, kABPersonLastNameProperty, (__bridge CFStringRef)(petLastName), nil);

简单的介绍:

  • ABRecordSetValue()把ABREcordRef作为第一个参数,它的记录是pet
  • 第二个参数是ABPropertyID,这个是API定义的,因为你想设置姓,所以传入kABPersonFirstNameProperty
  • 对于名,类似地传入kABPersonLastNameProperty

第三个参数看起来困惑吗?它是一个CFTypeRef,该类型包括了CFStringRef和ABMultiValueRef,你需要传递CFStringRef,但是你之后NSString。为了将NSString 转换成CFTypeRef,使用(__bridge CFStringRef) myString。

手机号的稍微复杂一点,因为一个联系人可以有多个手机号(家庭,手机,等等),因此这个必须使用ABMutableMultiValueRef。这些可以通过下面的代码完成,(在上面代码后面继续添加):

    ABMutableMultiValueRef phoneNumbers = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(phoneNumbers, (__bridge CFTypeRef)(petphoneNumber), kABPersonPhoneMainLabel, NULL);

当你声明ABMutableMultiValueRef,你必须说明是什么属性。在这里面,你想它是kABPersonPhoneProperty。第二行是添加pet's Phone number,这里注意你必须给这个号码一个label.这个label kABPersonPhoneMainLabel 说明这个号码是用户最主要的号码。然后是添加照片:

ABPersonSetImageData(pet, (__bridge CFDataRef)petImageData, nil);

最后是将联系人的信息保存到通讯录里面:

ABAddressBookAddRecord(addressBookRef, pet, nil);
ABAddressBookSave(addressBookRef, nil);

接下来运行,然后点击每个按钮,就可以将内容存储到自己本机的通讯录里面了。

但是你会发现一个问题,如果一致点击某个按钮,那么这个宠物的信息就会一直往通讯录里面添加。为了避免复制,你应该循环访问所有的通讯录信息确保新的通讯录记录名字不在通讯录里面。

插入以下代码到ABAddressBookAddRecord() 。首先,添加这一行:

NSArray *allContracts = (__bridge NSArray *)(ABAddressBookCopyArrayOfAllPeople(addressBookRef));

这里可以注意到:你可以使用__bridge将对象在Core Foundation对象转换成Foundation,也可以将Foundation转成Core Foundation。

然后,添加以下代码:

    for (id record in allContracts) {
ABRecordRef thisContract = (__bridge ABRecordRef)(record);
if (CFStringCompare(ABRecordCopyCompositeName(thisContract), ABRecordCopyCompositeName(pet), ) == kCFCompareEqualTo) {
//用户已经存在
NSLog(@"用户已经存在");
break; }
}

你必须使用id,因为从技术上来讲,Core Foundation类型是不能被转换成NSArray的,因为他们不是对象。ABRecordRefs被伪装成id来避免出错。所以为了得到ABRecordRef,还需要使用再次使用__bridge。

使用CFStringCompare的方式类似于NSString的isEqualToString。ABRecordCopyCompositeName得到了全名,它是联系人姓和名的组合。这样就可以用来防止重复记录了。

多线程

截止到这里上面的整体代码如下:

- (IBAction)tapAction:(UIButton *)sender {
if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted) {
//
NSLog(@"Denied");
}else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
//
NSLog(@"Authorized");
}else {
//
ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) {
if (granted) {
NSLog(@"Just authorized");
}else {
NSLog(@"Just deieny");
}
});
} NSString *petFirstName;
NSString *petLastName;
NSString *petphoneNumber;
NSData *petImageData;
if (sender.tag == ) {
petFirstName = @"Cheesy";
petLastName = @"Cat";
petphoneNumber = @"";
petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Cheesy.jpg"], 0.7f);
}else if (sender.tag == ) {
petFirstName = @"Freckles";
petLastName = @"Dog";
petphoneNumber = @"";
petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Freckles.jpg"], 0.7f);
}else if (sender.tag == ) {
petFirstName = @"Maxi";
petLastName = @"Dog";
petphoneNumber = @"";
petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Maxi.jpg"], 0.7f);
}else if(sender.tag == ) {
petFirstName = @"Shippo";
petLastName = @"Dog";
petphoneNumber = @"";
petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Shippo.jpg"], 0.7f);
}
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, nil); //通讯录
ABRecordRef pet = ABPersonCreate(); //一条记录 //设置姓名
ABRecordSetValue(pet, kABPersonFirstNameProperty, (__bridge CFStringRef)(petFirstName), nil);
ABRecordSetValue(pet, kABPersonLastNameProperty, (__bridge CFStringRef)(petLastName), nil); //设置手机号
ABMutableMultiValueRef phoneNumbers = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(phoneNumbers, (__bridge CFTypeRef)(petphoneNumber), kABPersonPhoneMainLabel, NULL);
ABRecordSetValue(pet, kABPersonPhoneProperty, phoneNumbers, nil); //设置照片
CFErrorRef *error;
ABPersonSetImageData(pet, (__bridge CFDataRef)petImageData, error); //获取所有联系人
NSArray *allContracts = (__bridge NSArray *)(ABAddressBookCopyArrayOfAllPeople(addressBookRef));
for (id record in allContracts) {
ABRecordRef thisContract = (__bridge ABRecordRef)(record);
if (CFStringCompare(ABRecordCopyCompositeName(thisContract), ABRecordCopyCompositeName(pet), ) == kCFCompareEqualTo) {
//用户已经存在
NSLog(@"用户已经存在");
break; }
} ABAddressBookAddRecord(addressBookRef, pet, nil);
ABAddressBookSave(addressBookRef, nil); // //iOS 8 and before
// if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted) {
// NSLog(@"Denied");
// }else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
// NSLog(@"Authorized");
// }else {
// ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) {
// if (granted) {
// NSLog(@"Just authorized");
// }else {
// NSLog(@"Just deieny");
// }
// });
// }
//
//// //iOS 9 and later
//// CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
//// if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {
//// NSLog(@"Denied");
//// }else if (status == CNAuthorizationStatusAuthorized ) {
//// NSLog(@"Authorized");
//// }else {
//// NSLog(@"Not determined");
//// }
}

这里还有个隐藏的问题,如果你看了ABAddressBookRequestAccessWithCompletion的官方文档,刚才的点击事件是在任意的队列上调用的。换句话说,也就是它执行可能在其他的线程上,不一定在主线程。

这里面你必须要知道:用户图形界面展示只能在主线程上。你必须确保任何影响用户图像化界面显示的代码都要在主线程上调用。

使用下面的代码可以很容易的完成。在ABAddressBookRequestWithCompletion之前使用:

    dispatch_async(dispatch_get_main_queue(), ^{
<#code#>
});

这个是在主线程上执行,可以使用用户图形化展示。如果想学习更多,可以阅读这里

然后使用上述block块进行如下操作:

ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!granted){
//
UIAlertView *cantAddContactAlert = [[UIAlertView alloc] initWithTitle: @"Cannot Add Contact" message: @"You must give the app permission to add the contact first." delegate:nil cancelButtonTitle: @"OK" otherButtonTitles: nil];
[cantAddContactAlert show];
return;
}
//5
//添加通讯录操作
});
});

这是最好的方法去请求用户获取通讯录权限,最好的实践就是在你真正用到的时候才去请求权限。如果你在启动的时候就请求用户权限,用户就会怀疑,因为用户不知道你为什么要用到通讯录。

还有一个问题就是关于ABAddressBookRequestAccessWithCompletion,如果用户给了APP权限,有的时候需要有5-10s的延迟,直到回调被调用。这看起来好像当我们在添加通讯录记录的饿时候程序是卡的状态。在大多数情况下,这种问题并不常见。

iOS Address Book指南的更多相关文章

  1. 李洪强iOS之集成极光推送二iOS 证书 设置指南

    李洪强iOS之集成极光推送二iOS 证书 设置指南 创建应用程序ID 登陆 iOS Dev Center 选择进入iOS Provisioning Portal. 在 iOS Provisioning ...

  2. 《大话移动APP测试:Android与iOS应用测试指南》

    <大话移动app测试:android与ios应用测试指南> 基本信息 作者: 陈晔 出版社:清华大学出版社 ISBN:9787302368793 上架时间:2014-7-7 出版日期:20 ...

  3. 推荐——Monkey《大话 app 测试——Android、iOS 应用测试指南》

    <大话移动——Android与iOS应用测试指南> 京东可以预购啦!http://item.jd.com/11495028.html 当当网:http://product.dangdang ...

  4. IOS设备设计完整指南

    作为初学者,常常不知如何下手设计,IOS应用UI设计中碰到的种种基础小问题,在此都将一一得到解答.这份完整的设计指南将带你快速上手,为IOS设计出优雅的应用吧. 关于此设计指南 此设计指南描述的是如何 ...

  5. iOS多线程编程指南

    iOS多线程编程指南(拓展篇)(1) 一.Cocoa 在Cocoa上面使用多线程的指南包括以下这些: (1)不可改变的对象一般是线程安全的.一旦你创建了它们,你可以把这些对象在线程间安全的传递.另一方 ...

  6. (译)IOS block编程指南 1 介绍

    Introduction(介绍) Block objects are a C-level syntactic and runtime feature. They are similar to stan ...

  7. iOS ---Extension编程指南

    当iOS 8.0和OS X v10.10发布后,一个全新的概念出现在我们眼前,那就是应用扩展.顾名思义,应用扩展允许开发者扩展应用的自定义功能和内容,能够让用户在使用其他app时使用该项功能.你可以开 ...

  8. iOS多线程编程指南(二)线程管理

    当应用程序生成一个新的线程的时候,该线程变成应用程序进程空间内的一个实体.每个线程都拥有它自己的执行堆栈,由内核调度独立的运行时间片.一个线程可以和其他线程或其他进程通信,执行I/O操作,甚至执行任何 ...

  9. iOS App提交指南-协议、税务和银行业务

    App通过审核时,选择的是手动发布,想着等到自己生日那天来发布,当做留个纪念,结果生日当天发布时,由于App属于收费应用,还需要填写协议.税务和银行信息,结果又急急忙忙地去找了下这方面的资料,现在把整 ...

随机推荐

  1. Javascript动画效果(四)

    Javascript动画效果(四) 前面我们自己写了一个小小的关于js动画的插件,下面我们来使用之前的框架来完成我们想要的动画效果.我们经常在淘宝网中看到,鼠标经过某一图片时,该图片有从上滚出而又从下 ...

  2. 主成分分析(PCA)的一种直观理解

    源自知乎的一个答案,网上很多关于PCA的文章,不过很多都只讲到了如何理解方差的投影,却很少有讲到为什么特征向量就是投影方向.本文从形象角度谈一谈,因为没有证明,所以不会严谨,但是应该能够帮助形象理解P ...

  3. 如何提升我的HTML&CSS技术,编写有结构的代码

    前言 之前写了四篇HTML和CSS的知识点,也相当于是一个知识点汇总.有需要的可以收藏,平时开发过程中应该会遇到这些点,到时候再查看这些博客可能更容易理解.从这篇开始更多的介绍开发过程经常让人头痛的前 ...

  4. [译]学习IPython进行交互式计算和数据可视化(三)

    第二章 在本章中,我们将详细学习IPython相对以Python控制台带来的多种改进.特别的,我们将会进行下面的几个任务: 从IPython中使用系统shell以在shell和Python之间进行强大 ...

  5. C#基础04

    介绍:泛型介绍,索引,Foreach遍历的解释,yield方法,path文件操作,Directory类基本操作<目录> 一:泛型   百度资料:泛型是 2.0 版 C# 语言和公共语言运行 ...

  6. 快速击键(MyEclipse编写的QuickHit项目)

    public class Level { private int levelNo;// 各级别编号 private int strLength;// 各级别一次输出字符串的长度 private int ...

  7. LINQ to SQL语句(4)之Join

    适用场景:在我们表关系中有一对一关系,一对多关系,多对多关系等.对各个表之间的关系,就用这些实现对多个表的操作. 说明:在Join操作中,分别为Join(Join查询), SelectMany(Sel ...

  8. [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(4)

    这个系列已经写了6篇,链接地址如下: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Auto ...

  9. JavaScript星形评分

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  10. webAPI 数组参数

    今天终于解决了一个坑的问题. 写了一个接口,调试了好几天都没成功. 一直以为是我的错误,直到我们部门的大牛来告诉我不是我的错误,是前端传参数有问题. 应该是参数是数组参数的问题,可能不好拼接,才导致接 ...