一、簡介
1、如何訪問用戶的通訊錄
1)在iOS9之前
有2個框架可以訪問用戶的通訊錄
AddressBookUI.framework
提供了聯系人列表界面、聯系人詳情界面、添加聯系人界面等
一般用於選擇聯系人
AddressBook.framework
純C語言的API,僅僅是獲得聯系人數據
沒有提供UI界面展示,需要自己搭建聯系人展示界面
里面的數據類型大部分基於Core Foundation框架,使用起來極其蛋疼
從iOS6開始,需要得到用戶的授權才能訪問通訊錄,因此在使用之前,需要檢查用戶是否已經授權
獲得通訊錄的授權狀態:ABAddressBookGetAuthorizationStatus()
2)在iOS9之后
也有2個框架可以訪問用戶的通訊錄
ContactsUI.framework
提供了聯系人列表界面、聯系人詳情界面、添加聯系人界面等
一般用於選擇聯系人
Contacts.framework
沒有提供UI界面展示,需要自己搭建聯系人展示界面
2、授權狀態
kABAuthorizationStatusNotDetermined
用戶還沒有決定是否授權你的程序進行訪問
kABAuthorizationStatusRestricted
iOS設備上一些許可配置阻止程序與通訊錄數據庫進行交互
kABAuthorizationStatusDenied
用戶明確的拒絕了你的程序對通訊錄的訪問
kABAuthorizationStatusAuthorized
用戶已經授權給你的程序對通訊錄進行訪問
3、申請訪問通訊錄
// 實例化通訊錄對象 ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL); ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { if (granted) { NSLog(@"授權成功!"); } else { NSLog(@"授權失敗!"); } }); CFRelease(addressBook);
提示:申請通訊錄訪問授權的代碼,通常放在AppDelegate中
二、訪問聯系人信息
1、聯系人屬性定義
所有的屬性常量值都定義在了ABPerson.h頭文件中
聯系人屬性包括以下類型:
簡單屬性:姓、名等
多重屬性:電話號碼、電子郵件等
組合屬性:地址等
注意:使用ABRecordCopyValue可以從一條Person記錄中獲取到對應的記錄,但是后續處理則需要根據記錄的具體類型加以區分
2、簡單屬性
一個聯系人就是一個ABRecordRef,每個聯系人都有自己的屬性,比如名字、電話、郵件等
使用ABRecordCopyValue函數可以從ABRecordRef中獲得聯系人的簡單屬性(例如:一個字符串)
ABRecordCopyValue函數接收2個參數
第1個參數是ABRecordRef實例
第2個參數是屬性關鍵字,定義在ABPerson.h中
ABPersonCopyLocalizedPropertyName函數可以根據指定的關鍵字獲取對應的標簽文本
3、獲得所有的聯系人數據
// 獲取所有聯系人記錄 CFArrayRef array = ABAddressBookCopyArrayOfAllPeople(addressBook); NSInteger count = CFArrayGetCount(array); for (NSInteger i = 0; i < count; ++i) { // 取出一條記錄 ABRecordRef person = CFArrayGetValueAtIndex(array, i); // 取出個人記錄中的詳細信息 // 名 CFStringRef firstNameLabel = ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty); CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty); CFStringRef lastNameLabel = ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty); // 姓 CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty); NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName); }
4、CoreFoundation 與 Foundation之間的橋接
// 1. 獲取通訊錄引用 ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil); // 2. 獲取所有聯系人記錄 NSArray *array = (__bridge NSArray *)(ABAddressBookCopyArrayOfAllPeople(addressBook)); for (NSInteger i = 0; i < array.count; i++) { // 取出一條記錄 ABRecordRef person = (__bridge ABRecordRef)(array[i]); // 取出個人記錄中的詳細信息 NSString *firstNameLabel = (__bridge NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonFirstNameProperty)); NSString *firstName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonFirstNameProperty)); NSString *lastNameLabel = (__bridge NSString *)(ABPersonCopyLocalizedPropertyName(kABPersonLastNameProperty)); NSString *lastName = (__bridge NSString *)(ABRecordCopyValue(person, kABPersonLastNameProperty)); NSLog(@"%@ %@ - %@ %@", lastNameLabel, lastName, firstNameLabel, firstName); } CFRelease(addressBook);
5、多重屬性
聯系人的有些屬性值就沒這么簡單,一個屬性可能會包含多個值
比如郵箱,分為工作郵箱、住宅郵箱、其他郵箱等
比如電話,分為工作電話、住宅電話、其他電話等
如果是復雜屬性,那么ABRecordCopyValue函數返回的就是ABMultiValueRef類型的數據,例如郵箱或者電話
// 取電話號碼 ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty); // 取記錄數量 NSInteger phoneCount = ABMultiValueGetCount(phones); // 遍歷所有的電話號碼 for (NSInteger i = 0; i < phoneCount; i++) { …
6、獲取復雜屬性的方法
// 電話標簽 CFStringRef phoneLabel = ABMultiValueCopyLabelAtIndex(phones, i); // 本地化電話標簽 CFStringRef phoneLocalLabel = ABAddressBookCopyLocalizedLabel(phoneLabel); // 電話號碼 CFStringRef phoneNumber = ABMultiValueCopyValueAtIndex(phones, i);
三、添加修改操作
1、添加聯系人的步驟
通過ABPersonCreate函數創建一個新的聯系人(返回ABRecordRef)
通過ABRecordSetValue函數設置聯系人的屬性
通過ABAddressBookAddRecord函數將聯系人添加到通訊錄數據庫中
通過ABAddressBookSave函數保存剛才所作的修改
可以通過ABAddressBookHasUnsavedChanges函數判斷是否有未保存的修改
當決定是否更改通訊錄數據庫后,你可以分別使用 AbAddressBookSave 或 ABAddressBookRevert 方式來保存或放棄更改
2、添加群組的步驟
添加群組的步驟大體和添加聯系人一致
通過ABPersonCreate函數創建一個新的組(返回ABRecordRef)
通過ABRecordSetValue函數設置組名
通過ABAddressBookAddRecord函數將組添加到通訊錄數據庫中
通過ABAddressBookSave函數保存剛才所作的修改
3、操作聯系人的頭像
想操作聯系人的頭像,有以下函數
BPersonHasImageData
判斷通訊錄中的聯系人是否有圖片
ABPersonCopyImageData
取得圖片數據(假如有的話)
ABPersonSetImageData
設置聯系人的圖片數據
四、實戰演練
1、iOS9之前有界面
#import <AddressBookUI/AddressBookUI.h> // 1.創建一個選擇聯系人的控制器 ABPeoplePickerNavigationController *ppnc = [[ABPeoplePickerNavigationController alloc] init]; // 2.設置代理 ppnc.peoplePickerDelegate = self;//<ABPeoplePickerNavigationControllerDelegate> // 3.彈出控制器 [self presentViewController:ppnc animated:YES completion:nil]; #pragma mark - 實現ABPeoplePickerNavigationController的代理方法 /** * 當用戶選擇某一個聯系人的時候會執行該方法(如果實現了該方法,那么一定不會執行下面的代理方法) * * @param person 選中的聯系人 */ - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person { // 1.獲取用戶的姓名 CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty); CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty); // 將CoreFoundation框架的對象轉成Foundation框架對象,那么可以通過橋接的方式 // 如果是CoreFoundation框架中的對象,如果是通過copy或者create或者retain,必須對應有一個release /* __bridge type: 通過該橋接方式,那么CoreFoundation對應的對象需要手動來釋放,Foundation框架的對象如果是在ARC環境下面,則不需手動釋放 __bridge_transfer type: 通過該橋接方式,那么CoreFoundation對應的對象表示已經交給Foundation對象進行管理,如果是在ARC環境下面,不需要釋放任何一個對象 */ NSString *lastname = (__bridge NSString *)(lastName); NSString *firstname = (__bridge_transfer NSString *)(firstName); NSLog(@"%@ %@", lastname, firstname); // 2.獲取電話號碼 // 3.釋放對象 CFRelease(lastName); } /** * 當用戶選擇某一個聯系人的某一個屬性的時候會執行該方法 * * @param person 選中的聯系人 * @param property 選中的聯系人的屬性 * @param identifier 每一個屬性都有一個對應的表示 */ - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier { NSLog(@"%s", __func__); } /** * 當點擊取消按鈕時,會執行該方法 * */ - (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker { NSLog(@"%s", __func__); }
2、iOS9之前無界面
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 1.獲取授權狀態 ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus(); // 2.判斷授權狀態 if (status == kABAuthorizationStatusNotDetermined) { // 3.請求授權 // 3.1.創建通信錄對象 ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL); // 3.2.請求授權 ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) { // 當用戶決定是否授權的時候會執行該block if (granted) { // 授權成功 NSLog(@"可以訪問通信錄"); } else { // 授權失敗 NSLog(@"不可以訪問通信錄"); } }); // 3.3.釋放不再使用的對象 CFRelease(addressBook); } return YES; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 1.判斷授權狀態 ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus(); if (status != kABAuthorizationStatusAuthorized) return; // 2.獲取聯系人 // 2.1.創建通信錄對象 ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL); // 2.2.獲取所有的聯系人 CFArrayRef peopleArray = ABAddressBookCopyArrayOfAllPeople(addressBook); // 2.3.遍歷所有的聯系人 CFIndex peopleCount = CFArrayGetCount(peopleArray); for (int i = 0; i < peopleCount; i++) { // 3.獲取一條記錄 ABRecordRef person = CFArrayGetValueAtIndex(peopleArray, i); // 3.1.獲取聯系人的姓名 NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty); NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty); NSLog(@"%@ %@", firstName, lastName); // 3.2.獲取電話號碼 // 3.2.1.獲取所有的電話 ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty); // 3.3.2.遍歷所有的電話號碼 CFIndex phoneCount = ABMultiValueGetCount(phones); for (int i = 0; i < phoneCount; i++) { NSString *phoneLabel = (__bridge_transfer NSString *)ABMultiValueCopyLabelAtIndex(phones, i); NSString *phoneValue = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phones, i); NSLog(@"%@ %@", phoneLabel, phoneValue); } } }
3、三方框架RHAddressBook
#import <AddressBook/AddressBook.h> #import <RHAddressBook/AddressBook.h> - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 1.獲取授權狀態 RHAuthorizationStatus status = [RHAddressBook authorizationStatus]; // 2.判斷授權狀態 if (status == RHAuthorizationStatusNotDetermined) { // 3.請求授權 // 3.1.創建通信錄對象 RHAddressBook *addressBook = [[RHAddressBook alloc] init]; // 3.2.請求授權 [addressBook requestAuthorizationWithCompletion:^(bool granted, NSError *error) { if (granted) { NSLog(@"授權成功"); } else { NSLog(@"授權失敗"); } }]; } return YES; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 1.獲取授權狀態 RHAuthorizationStatus status = [RHAddressBook authorizationStatus]; // 2.如果是已經授權,才能獲取聯系人 if (status != RHAuthorizationStatusAuthorized) return; // 3.創建通信錄對象 RHAddressBook *addressBook = [[RHAddressBook alloc] init]; // 4.獲取所有的聯系人 NSArray *peopleArray = addressBook.people; // 5.遍歷所有的記錄 for (RHPerson *person in peopleArray) { // 5.1.獲取用戶的姓名 NSString *firstname = person.firstName; NSString *lastname = person.lastName; NSLog(@"%@ %@", firstname, lastname); // 5.2.獲取電話號碼 RHMultiValue *phones = person.phoneNumbers; // 5.3.遍歷所有的電話號碼 for (int i = 0; i < phones.count; i++) { NSString *phoneLabel = [phones labelAtIndex:i]; NSString *phoneValue = [phones valueAtIndex:i]; NSLog(@"%@ %@", phoneLabel, phoneValue); } } }
4、iOS9之后有界面
#import <ContactsUI/ContactsUI.h> <CNContactPickerDelegate> // 1.創建選擇聯系人的界面 CNContactPickerViewController *cpvc = [[CNContactPickerViewController alloc] init]; // 2.設置代理 cpvc.delegate = self; // 3.彈出控制器 [self presentViewController:cpvc animated:YES completion:nil]; #pragma mark - 實現CNContactPickerViewController的代理方法 /** * 當用戶選中某一個聯系人的時候會執行該方法 * * @param contact 選中的聯系人 */ - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact { // 1.獲取聯系人的姓名 NSString *lastname = contact.familyName; NSString *firstname = contact.givenName; NSLog(@"%@ %@", lastname, firstname); // 2.獲取電話號碼 for (CNLabeledValue *labelValue in contact.phoneNumbers) { // 3.獲取電話的label/value NSString *phoneLabel = labelValue.label; CNPhoneNumber *phoneNumber = labelValue.value; NSString *phoneValue = phoneNumber.stringValue; NSLog(@"%@ %@", phoneLabel, phoneValue); } } /** * 當用戶選中某一個聯系人的某一個屬性時候會執行該方法 * * @param contactProperty 選中的屬性 */ - (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty { NSLog(@"%s", __func__); }
5、iOS9之后無界面
#import <Contacts/Contacts.h> - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 1.獲取授權狀態 CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts]; // 2.判斷授權狀態,如果是未決定請求授權 if (status == CNAuthorizationStatusNotDetermined) { // 3.請求授權 // 3.1.創建CNContactStore對象 CNContactStore *store = [[CNContactStore alloc] init]; // 3.2.請求授權 [store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) { if (error) { NSLog(@"%@", error); return; } if (granted) { NSLog(@"授權成功"); } else { NSLog(@"授權失敗"); } }]; } return YES; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 1.判斷授權狀態 CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts]; if (status != CNAuthorizationStatusAuthorized) return; // 2.創建通信錄對象 CNContactStore *store = [[CNContactStore alloc] init]; // 3.請求所有的聯系人 // 3.1.創建聯系人請求對象,並且傳入keys:你准備獲取的信息(姓familyName名givenName 電話號碼:phones) NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]; CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys]; // 3.2.請求所有的聯系人 NSError *error = nil; [store enumerateContactsWithFetchRequest:request error:&error usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) { // 當遍歷到一條記錄就會執行該block // 4.獲取聯系人 // 4.1.獲取姓名 NSString *firstName = contact.givenName; NSString *lastName = contact.familyName; NSLog(@"%@ %@", firstName, lastName); // 4.2.獲取電話號碼 NSArray *phones = contact.phoneNumbers; for (CNLabeledValue *labelValue in phones) { NSString *phoneLabel = labelValue.label; CNPhoneNumber *phoneNumber = labelValue.value; NSString *phoneValue = phoneNumber.stringValue; NSLog(@"%@ %@", phoneLabel, phoneValue); } }]; }