iOS Address Book指南
尽管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");
// }
}
让我们来分析一下:
- 这个检查是用来检测用户是否拒绝了你的app访问手机通讯录,或者是它是受限制的(比如家长控制).如果用户拒绝了或者限制了,那么你只能告诉用户没有权限对通讯录进行操作,其他的什么也无法做。
- 这个检查是看看用户是否已经允许你的APP访问用户的通讯录,如果允许了,你可以随意地修改或者对通讯录进行其他操作。
- 这个检查是看用户是否还没有确定你的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指南的更多相关文章
- 李洪强iOS之集成极光推送二iOS 证书 设置指南
李洪强iOS之集成极光推送二iOS 证书 设置指南 创建应用程序ID 登陆 iOS Dev Center 选择进入iOS Provisioning Portal. 在 iOS Provisioning ...
- 《大话移动APP测试:Android与iOS应用测试指南》
<大话移动app测试:android与ios应用测试指南> 基本信息 作者: 陈晔 出版社:清华大学出版社 ISBN:9787302368793 上架时间:2014-7-7 出版日期:20 ...
- 推荐——Monkey《大话 app 测试——Android、iOS 应用测试指南》
<大话移动——Android与iOS应用测试指南> 京东可以预购啦!http://item.jd.com/11495028.html 当当网:http://product.dangdang ...
- IOS设备设计完整指南
作为初学者,常常不知如何下手设计,IOS应用UI设计中碰到的种种基础小问题,在此都将一一得到解答.这份完整的设计指南将带你快速上手,为IOS设计出优雅的应用吧. 关于此设计指南 此设计指南描述的是如何 ...
- iOS多线程编程指南
iOS多线程编程指南(拓展篇)(1) 一.Cocoa 在Cocoa上面使用多线程的指南包括以下这些: (1)不可改变的对象一般是线程安全的.一旦你创建了它们,你可以把这些对象在线程间安全的传递.另一方 ...
- (译)IOS block编程指南 1 介绍
Introduction(介绍) Block objects are a C-level syntactic and runtime feature. They are similar to stan ...
- iOS ---Extension编程指南
当iOS 8.0和OS X v10.10发布后,一个全新的概念出现在我们眼前,那就是应用扩展.顾名思义,应用扩展允许开发者扩展应用的自定义功能和内容,能够让用户在使用其他app时使用该项功能.你可以开 ...
- iOS多线程编程指南(二)线程管理
当应用程序生成一个新的线程的时候,该线程变成应用程序进程空间内的一个实体.每个线程都拥有它自己的执行堆栈,由内核调度独立的运行时间片.一个线程可以和其他线程或其他进程通信,执行I/O操作,甚至执行任何 ...
- iOS App提交指南-协议、税务和银行业务
App通过审核时,选择的是手动发布,想着等到自己生日那天来发布,当做留个纪念,结果生日当天发布时,由于App属于收费应用,还需要填写协议.税务和银行信息,结果又急急忙忙地去找了下这方面的资料,现在把整 ...
随机推荐
- git入门到熟练使用
最近以为接触ios开发,所以对git也产生了一点兴趣.所以在网上搜索资料开始学习,但大部分都是没用的copy的文章,有一个还不错的,推荐给大家 http://www.liaoxuefeng.com/w ...
- 30天C#基础巩固------了解委托,string练习
---->了解委托. 生活中的例子:我要打官司,我需要找一个律师,法庭上面律师为当事人辩护,它真正执行的是当事人的陈词,这时律师 就相当于一个委托对象.当事人则委托律师为自己辩解. ...
- entity framework 5 更新指定字段
dbSet.Attach(good); var stateEntry = ((IObjectContextAdapter)context).ObjectContext. ObjectStateMana ...
- ASP.NET MVC系列:添加模型的验证规则
首先,在模型类中引用 System.ComponentModel.DataAnnotations 命名空间;System.ComponentModel.DataAnnotations 命名空间提供定义 ...
- 【吐槽】IM群里几种我认为愚蠢的提问方式
一.“有人吗?” 你能得到一句[在,请说]的答复我就服了你,这样问的结果往往是等半天没一个人鸟你,悲观的你或者就此凄凉的退群了,感概人情冷暖的同时甚至开始怀疑人生:积极的你或者这才意识到~要不干脆说问 ...
- NoSQL数据库介绍
NoSQL在2010年风生水起,大大小小的Web站点在追求高性能高可靠性方面,不由自主都选择了NoSQL技术作为优先考虑的方面.今年伊始,InfoQ中文站有幸邀请到凤凰网的孙立先生,为大家分享他之于N ...
- ASP.NET三层架构之不确定查询参数个数的查询
在做三层架构的时候,特别是对表做查询的时候,有时候并不确定查询条件的个数,比如查询学生表:有可能只输入学号,或者姓名,或者性别,总之查询条件的参数个数并不确定,下面是我用List实现传值的代码: 附图 ...
- Type mismatch: cannot convert from java.sql.PreparedStatement to com.mysql.jdbc.PreparedStatement
Connection.prepareStatement()函数出错,提示: Type mismatch: cannot convert from java.sql.PreparedStatement ...
- Eclipse统计代码行数
开发过程中,经常需要统计代码行数,这时可以通过Eclipse的Search功能来实现. 步骤: 1.在Package Explorer中选中需要统计的包: 2.单击菜单Search-->File ...
- CentOS 6.5下Redis安装详细步骤
Redis简介: Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发工 ...