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,你可以交流来您的仓储的爱侣。

恳请权限

在二〇一二年,有一个争议: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:一旦用户点击了授权按钮,便会调用里面的事物。

这一次运行的结果就是:

图片 1

当你点击了Don’t
Allow,就标志iDenied了,即便ok就代表你允许了APP访问通信录。

创造记录

现行,让大家起首去成立通信录记录。大家前些天清空按钮点击事件,然后重写它。在重写的那么些点子里面,你需求创立一个ABRecordRef,他概括了宠物的属性,检查一下通讯录确保不设有你添加的关系人,若是宠物不在通信录,就把她参与到通信录。

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

  NSString *petFirstName;
    NSString *petLastName;
    NSString *petphoneNumber;
    NSData *petImageData;
    if (sender.tag == 1) {
        petFirstName = @"Cheesy";
        petLastName = @"Cat";
        petphoneNumber = @"201345345345";
        petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Cheesy.jpg"], 0.7f);
    }else if (sender.tag == 2) {
        petFirstName = @"Freckles";
        petLastName = @"Dog";
        petphoneNumber = @"2015434345345";
        petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Freckles.jpg"], 0.7f);
    }else if (sender.tag == 3) {
        petFirstName = @"Maxi";
        petLastName = @"Dog";
        petphoneNumber = @"1243504354";
        petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Maxi.jpg"], 0.7f);
    }else if(sender.tag == 4) {
        petFirstName = @"Shippo";
        petLastName = @"Dog";
        petphoneNumber = @"5406957657";
        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), 0) == kCFCompareEqualTo) {
            //用户已经存在
            NSLog(@"用户已经存在");
            break;

        }
    }

你不可以不使用id,因为从技术上来讲,Core
Foundation类型是不可能被转换成NSArray的,因为他们不是目的。ABRecordRefs被伪装成id来幸免失误。所以为了取得ABRecordRef,还亟需运用重复使用__bridge。

利用CFStringCompare的艺术接近于NSString的isEqualToString。ABRecordCopyCompositeName获得了人名,它是调换人姓和名的结合。那样就足以用来幸免重复记录了。

多线程

利落到这里上面的全体代码如下:

图片 2图片 3

- (IBAction)tapAction:(UIButton *)sender {
    if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied ||ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted) {
        //1
        NSLog(@"Denied");
    }else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
        //2
        NSLog(@"Authorized");
    }else {
        //3
        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 == 1) {
        petFirstName = @"Cheesy";
        petLastName = @"Cat";
        petphoneNumber = @"201345345345";
        petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Cheesy.jpg"], 0.7f);
    }else if (sender.tag == 2) {
        petFirstName = @"Freckles";
        petLastName = @"Dog";
        petphoneNumber = @"2015434345345";
        petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Freckles.jpg"], 0.7f);
    }else if (sender.tag == 3) {
        petFirstName = @"Maxi";
        petLastName = @"Dog";
        petphoneNumber = @"1243504354";
        petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Maxi.jpg"], 0.7f);
    }else if(sender.tag == 4) {
        petFirstName = @"Shippo";
        petLastName = @"Dog";
        petphoneNumber = @"5406957657";
        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), 0) == 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");
////    }
}

View Code

那边还有个藏匿的题材,如若您看了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){ 
      //4
      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的延迟,直到回调被调用。那看起来好像当我们在添加通信录记录的饿时候程序是卡的意况。在多数动静下,那种题材并不普遍。