AddressBook.framework引导

2019-04-15 15:32发布


title: AddressBook.framework
date: 2016-08-22 17:44:14
tags: Frameworks
thumbnail: http://www.51wendang.com/pic/00a226f3c9fa6ed45d1d781c/1-817-png_6_0_0_300_110_360_270_892.5_1263-1200-0-0-1200.jpg

AddressBook.framework/AddressBookUI.framework

9.0之后, AddressBook.frameworkContacts.framework代替。但是目前大部分的应用软件起支撑的版本是iOS6.0或7.0, 所以AddressBook还大有用处。之前在简书的文章AddressBook, AddressBookUI中有提及, 但是由于是转载, 所以代码不是很清晰, 而且有一些读者希望得到清晰的代码以及详细的功能解释, 所以在这里把AddressBook.framework以及AddressBookUI.framework重新做一下详细的使用方法介绍。 这篇文章先介绍AddressBook.frameworkAddressBookUI以及Contacts.framework也会补上。

开始使用AdressBook

首先, 导入我们需要的Framework #import 经常使用到:
  • ABAddressBookRef: 通讯录引用
  • ABRecordRef: 记录引用
  • ABPropertyID: 记录的属性ID

获取AddressBook使用权限

像使用相机、推送一样, 访问AddressBook同样需要获取权限。 在获取权限之前,我们需要创建一个AddressBook的引用,用来做后续操作。 static ABAddressBookRef r; static inline ABAddressBookRef getAddressBookRef() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ CFErrorRef errorRef; r = ABAddressBookCreateWithOptions(NULL, &errorRef); if ( errorRef ) { ILog(@"%@", (__bridge NSString *)CFErrorCopyFailureReason(errorRef)); } }); return r; } ABAddressBookRef,作为后续使用的通讯录引用,这里写作单例。或者可以写作单例的属性,但只需初始化一次。 ABAddressBookCreateWithOptions通过参数创建, options暂时为预留字段,但是AddressBook在9.0被弃用,估计不会被使用了。 在获取到引用之后,我们应先查询AddressBook的访问状态: Boolean needRequestAccess() { ABAuthorizationStatus s = ABAddressBookGetAuthorizationStatus(); if ( s == kABAuthorizationStatusDenied || s == kABAuthorizationStatusRestricted ) { alert(@"提示", @""); return false; } if ( s != kABAuthorizationStatusAuthorized ) { return true; } return false; } 推荐使用switch-case来判断状态。如果结果为kABAuthorizationStatusAuthorized表示可以正常访问。要注意的是,如果用户拒绝了首次的请求,那么需要用户在设置-隐私-通讯录中手动打开App使用通讯录的权限。 如果结果为kABAuthorizationStatusNotDetermined意味着我们需要请求访问权限: Restricted意味着系统决定了访问权限,用户不能修改。 void requestAddressBookAccess (ABAddressBookRequestAccessCompletionHandler handler) { ABAddressBookRequestAccessWithCompletion(getAddressBookRef(), handler); } 综合起来的调用: void initAddressBook (void (^shouldAccessAddressBook)(Boolean boo)) { if ( needRequestAccess() ) { requestAddressBookAccess(^(bool granted, CFErrorRef error) { if ( granted ) { shouldAccess = true; } shouldAccessAddressBook(granted); }); } else { shouldAccess = true; shouldAccessAddressBook(true); } } 非常简单的,我们获得了通讯录的访问权限。

用户的查询

用户的查询非常简单;
  • ABAddressBookGetPersonCount:获取总人数
  • ABAddressBookGetPersonWithRecordID:通过RecordID获取单个人,recordID可以通过ABRecordGetRecordID获得
  • ABAddressBookCopyArrayOfAllPeople:获取全部联系人数组
  • ABAddressBookCopyArrayOfAllPeopleInSource:获取记录引用中所有的练习嗯
  • ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering:带着排序参数
  • ABAddressBookCopyPeopleWithName:拷贝符合该名称的联系人
获取联系人的数量: CFIndex i = ABAddressBookGetPersonCount(getAddressBookRef()); printf("AddressBook: has %ld Person", i); 获取联系人: CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef()); ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0); ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty)); 获取联系人组使用ABAddressBookCopyArrayOfAllGroups即可。然后通过ABAddressBookCopyArrayOfAllPeopleInSource即可获得组内联系人。

用户信息的修改与删除

CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef()); ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0); ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty)); if ( ABRecordSetValue(pr, kABPersonFirstNameProperty, (__bridge CFStringRef)@"Hello", nil) ) { if ( ABAddressBookSave(getAddressBookRef(), nil) ) { ILog(@"Succeed!"); } } if ( ABAddressBookRemoveRecord(getAddressBookRef(), pr, nil) ) { if ( ABAddressBookSave(getAddressBookRef(), nil) ) { ILog(@"Remove Succeed!"); } } ABRecordCopyValue用来通过ABPropertyID获取相应属性的内容, 具体的ID在ABPerson.h中有详细列表。 ABRecordSetValue用来通过ABPropertyID设置相应的属性内容,同时返回bool值以供判段。参数中的error已经不在使用。 在修改后,切记保存修改,使用ABAddressBookSave做保存,在这之前,可以使用ABAddressBookHasUnsavedChanges判断是否存在未保存的修改。 使用ABAddressBookRemoveRecord来移除记录。

监听其他应用对AddressBook的修改

在操作AddressBook的同时,有可能在后台的时候被其他程序所修改,addressBook提供了监听方法: void callBack(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) { ILog(@"AddressBook has changed in another application."); }; void handleChange() { ABAddressBookRegisterExternalChangeCallback(getAddressBookRef(), callBack, nil); } 在收到监听后我们需要做相应的处理,比如: 是否其他改动与我们的改动有重叠等。

注意点

  • 在设置值的时候,可以使用ABMultiValueRef设置多个值,比如说多个电话。
  • 在不使用的时候,要使用ABAddressBookUnregisterExternalChangeCallback取消对AddressBook的监听。
Q&A: Q:如何在删除完联系人的多个电话后,直接删除联系人? A:通过propertyID获取到电话的信息后,做简单的判断就可以实现。如果仅存这一条电话记录,那么在删除的同时也删除掉联系人即可。

本文全部的代码 #import "Ins_AddressBook.h" @implementation InsAddressBook static Boolean shouldAccess; static ABAddressBookRef r; static inline ABAddressBookRef getAddressBookRef() { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ CFErrorRef errorRef; r = ABAddressBookCreateWithOptions(NULL, &errorRef); if ( errorRef ) { ILog(@"%@", (__bridge NSString *)CFErrorCopyFailureReason(errorRef)); } }); return r; } void requestAddressBookAccess (ABAddressBookRequestAccessCompletionHandler handler) { ABAddressBookRequestAccessWithCompletion(getAddressBookRef(), handler); } Boolean needRequestAccess() { ABAuthorizationStatus s = ABAddressBookGetAuthorizationStatus(); if ( s == kABAuthorizationStatusDenied || s == kABAuthorizationStatusRestricted ) { alert(@"提示", @"你之前已经拒绝了程序的访问权限, 请在设置-隐私-通讯录中手动打开, 并重新启动应用。"); return false; } if ( s != kABAuthorizationStatusAuthorized ) { return true; } return false; } void initAddressBook (void (^shouldAccessAddressBook)(Boolean boo)) { if ( needRequestAccess() ) { requestAddressBookAccess(^(bool granted, CFErrorRef error) { if ( granted ) { shouldAccess = true; } shouldAccessAddressBook(granted); }); } else { shouldAccess = true; shouldAccessAddressBook(true); } } void callBack(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) { ILog(@"AddressBook has changed in another application."); }; void handleChange() { ABAddressBookRegisterExternalChangeCallback(getAddressBookRef(), callBack, nil); } void getPerson () { CFIndex i = ABAddressBookGetPersonCount(getAddressBookRef()); printf("AddressBook: has %ld Person", i); CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef()); ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0); ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty)); if ( ABRecordSetValue(pr, kABPersonFirstNameProperty, (__bridge CFStringRef)@"Hello", nil) ) { if ( ABAddressBookSave(getAddressBookRef(), nil) ) { ILog(@"Succeed!"); } } if ( ABAddressBookRemoveRecord(getAddressBookRef(), pr, nil) ) { if ( ABAddressBookSave(getAddressBookRef(), nil) ) { ILog(@"Remove Succeed!"); } } } - (instancetype)init { self = [super init]; if ( self ) { initAddressBook(^(Boolean boo){ if ( boo ) { ILog(@"Get access to addressBook") handleChange(); getPerson(); } else { ILog(@"Without access to addressBook") } }); } return self; } DEF_SINGLETON_AUTOLOAD(InsAddressBook) @end

AddressBookUI.framework

相比而言,使用UI则简单的多,直接进入创建一个新的用户: ABNewPersonViewController * pv = [[ABNewPersonViewController alloc] init]; pv.newPersonViewDelegate = self; [getRootNc() presentViewController:INS_NAV(pv) animated:YES completion:nil]; - (void) newPersonViewController: (ABNewPersonViewController *) newPersonView didCompleteWithNewPerson: (ABRecordRef) person { [newPersonView.navigationController dismissViewControllerAnimated:YES completion:nil]; } 创建界面与其代理方法,当然在ABNewPersonViewController中,有写参数我们可以设置,意义很简单,可以通过我们上边AddressBook.framework中获得的一些引用穿进去,或者在创建用户之前直接设置一个用户的基础信息。这里不做赘述。

选择一个用户

ABPeoplePickerNavigationController * pv = [[ABPeoplePickerNavigationController alloc] init]; pv.peoplePickerDelegate = self; [getRootNc() presentViewController:pv animated:YES completion:nil]; - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person { } // Called after the user has pressed cancel. - (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker { [peoplePicker dismissViewControllerAnimated:YES completion:nil]; }
注:这里我没有低版本的测试环境,但是可以看到,选中人的方法在8.0后才被使用,所以低版本的童鞋应该主动尝试原本被弃用的2个方法。

展示用户

ABPersonViewController
If displayedPerson has been added to an ABAddressBook, then the addressBook property will be updated to use the displayedPerson's ABAddressBook.
可以设置的属性包括:允许编辑、允许操作(短信、邮件等),允许展示连接的联系人,设置属性高亮等。也比较方便。

信息的完善

ABUnknownPersonViewController ABUnknownPersonViewController *un = [[ABUnknownPersonViewController alloc] init]; un.displayedPerson = person; // 展示的联系人 un.allowsAddingToAddressBook = YES; // 允许添加到通讯录中 [getRootNc() presentViewController:INS_NAV(un) animated:YES completion:nil];

总结

AddressBook.framework、AdressBookUI.framework还是可以满足一些基本需求,但是由于是c库,并且功能不是很完善,所以在iOS9.0之后苹果使用Contacts.framework来代替AddressBook.framework。ContactsFramework是一整套OC的库,理解起来也很简单。 这是ContactsFramework中包含的一些头文件,在使用AddressBook的时候,基本所有的方法都在后边写了使用ContactsFramework中什么方法来代替。 CNContact以及CNGroup分别代表了联系人与联系人组,比之前的Record引用清晰了许多。 CNContactFetchRequest以及CNSaveRequest方便的提供了查询以及保存等操作。 CNSaveRequest则提供了方便直观的方法去保存用户。 然后整个框架都清晰了很多,基本的使用方法如下: CNContactStore * c; CNAuthorizationStatus s; CNContactFetchRequest * f; NSError * e; s = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts]; c = [[CNContactStore alloc] init]; if ( s != CNAuthorizationStatusAuthorized ) { ILog(@"Un Authorized !"); [c requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * error) { }]; } // 根据`CNContact`中的属性单独获得,比如 @[CNContactGivenNameKey, CNContactMiddleNameKey, ...] f = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactMiddleNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey]]; BOOL b = [c enumerateContactsWithFetchRequest:f error:&e usingBlock:^(CNContact * contact, BOOL * stop) { /* !$_, value=>" ), emailAddresses=( "!$_, value=dylan@china.com>" ), postalAddresses=(not fetched)> */ }]; if ( b ) { ILog(@"Search success."); } 当然不能所有的代码全部我贴出来,关于保存等功能,大家自行探索。 2016-8-23 上午10:00 copyRight@dylan@china.com 欢迎转载。