蘋果的通訊錄功能在iOS7,iOS8,iOS9 都有著一定的不同,iOS7和8用的是 <AddressBookUI/AddressBookUI.h> ,但是兩個系統版本的代理方法有一些變化,有些代理方法都標註了 NS_DEPRECATED_IOS(2_0, 8_0) 並推薦了另一個代理方法與之對應。 ...
蘋果的通訊錄功能在iOS7,iOS8,iOS9 都有著一定的不同,iOS7和8用的是 <AddressBookUI/AddressBookUI.h> ,但是兩個系統版本的代理方法有一些變化,有些代理方法都標註了 NS_DEPRECATED_IOS(2_0, 8_0) 並推薦了另一個代理方法與之對應。 而iOS8到iOS9則是直接棄用了<AddressBookUI/AddressBookUI.h>取而代之的是<ContactsUI/ContactsUI.h>,後者是OC調用,據說當時蘋果宣佈棄用AddressBookUI還引來了陣陣歡呼。這也就是在使用通訊錄功能時得考慮版本各種判斷,我也就是工作中遇到了這種坑,然後就順手相容封裝了一下。希望能解決這個問題。
我覺得通訊錄這裡的類結構沒必要像SDWebImage或是Core Location這樣列出來詳細去說。大家用到通訊錄無外乎就三個功能:
1.點擊彈出通訊錄頁面,選擇了一個聯繫人的電話後直接將信息填到頁面輸入框內。
2.遍歷所有的通訊錄數據統一做批量操作,搭建新頁面或直接上傳。
3.給通訊錄寫入一條信息。
這裡會先對比一下iOS789的寫法,最後奉上demo(一個封裝後的庫,提供了非常便利的api)。不關心內部實現的朋友可以直接拉到demo部分。
一、首先是獲取通訊錄的許可權
iOS7和8保持一致
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, NULL);
if (status == kABAuthorizationStatusNotDetermined) {
NSLog(@"還沒問");
ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error){
if(granted){
NSLog(@"點擊同意");
}else{
NSLog(@"點擊拒絕");
}
});
}else if (status == kABAuthorizationStatusAuthorized){
NSLog(@"已經授權");
[self loadPerson];
}else {
NSLog(@"沒有授權");
// 彈窗提示去獲取許可權
}
iOS9及以後調用方法改成
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if (status == CNAuthorizationStatusNotDetermined) {
[[[CNContactStore alloc]init] requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
NSLog(@"還沒問");
if(granted){
NSLog(@"點擊了同意");
[self loadPerson];
}else{
NSLog(@"點擊了拒絕");
}
}];
}else if (status == CNAuthorizationStatusAuthorized){
NSLog(@已經授權");
}else {
NSLog(@"沒有授權");
}
二、彈出通訊錄選擇界面
iOS7的寫法如下,代理方法的返回值大多是BOOL類型。
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
return YES;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
ABMultiValueRef phone = ABRecordCopyValue(person, kABPersonPhoneProperty);
long index = ABMultiValueGetIndexForIdentifier(phone,identifier);
NSString *phoneNO = (__bridge NSString *)ABMultiValueCopyValueAtIndex(phone, index);
CFStringRef lastName = ABRecordCopyValue(person, kABPersonLastNameProperty);
CFStringRef firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *lastname = (__bridge_transfer NSString *)(lastName);
NSString *firstname = (__bridge_transfer NSString *)(firstName);
if (phone) {
[peoplePicker dismissViewControllerAnimated:YES completion:nil];
return NO;
}
return YES;
}
iOS8的代理方法換了,改成了下麵兩個,但是方法內部的取值基本相同
// 點擊了通訊錄名字就會退出
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person;
// 點擊了名字裡面的電話或郵箱才會退出
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier;
至於會調用哪一個方法,可以根據實際需要去選擇,在彈出界面的方法中predicateForSelectionOfPerson 這個屬性傳false就是調用下麵的。
ABPeoplePickerNavigationController *pickervc = [[ABPeoplePickerNavigationController alloc] init];
pickervc.predicateForSelectionOfPerson = [NSPredicate predicateWithValue:false];
pickervc.peoplePickerDelegate = self;
[target presentViewController:pickervc animated:YES completion:nil];
iOS9系統下的彈出選擇器方法 和 代理方法如下
// 彈出選擇器
- (void)presentPageOnTarget{
CNContactPickerViewController *contactVc = [[CNContactPickerViewController alloc] init];
contactVc.delegate = self;
[target presentViewController:contactVc animated:YES completion:nil];
}
// 代理方法
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact
{
SXPersonInfoEntity *personEntity = [SXPersonInfoEntity new];
NSString *lastname = contact.familyName;
NSString *firstname = contact.givenName;
NSLog(@"%@ %@", lastname, firstname);
personEntity.lastname = lastname;
personEntity.firstname = firstname;
NSMutableString *fullname = [[NSString stringWithFormat:@"%@%@",lastname,firstname] mutableCopy];
[fullname replaceOccurrencesOfString:@"(null)" withString:@"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, fullname.length)];
personEntity.fullname = fullname;
NSString *fullPhoneStr = [NSString string];
NSArray *phoneNums = contact.phoneNumbers;
for (CNLabeledValue *labeledValue in phoneNums) {
NSString *phoneLabel = labeledValue.label;
CNPhoneNumber *phoneNumer = labeledValue.value;
NSString *phoneValue = phoneNumer.stringValue;
NSLog(@"%@ %@", phoneLabel, phoneValue);
if (phoneValue.length > 0) {
fullPhoneStr = [fullPhoneStr stringByAppendingString:phoneValue];
fullPhoneStr = [fullPhoneStr stringByAppendingString:@","];
}
}
if (fullPhoneStr.length > 1) {
personEntity.phoneNumber = [fullPhoneStr substringToIndex:fullPhoneStr.length - 1];
}
self.chooseAction(personEntity);
}
這個是點擊了名字就直接回調的方法,如果希望點擊了屬性再回調,則需要加上這一行
contactVc.predicateForSelectionOfContact = [NSPredicate predicateWithValue:false];
// 代理方法調用
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty
三、獲取全部通訊錄信息
關於批量獲取所有通訊錄信息的方法有點冗長,這裡就不一一貼了,只貼下iOS9的寫法,iOS7和8的代碼demo里都有。
- (void)printAllPerson
{
// 獲取
CNContactStore *contactStore = [[CNContactStore alloc] init];
NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
// 遍歷
[contactStore enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
NSString *lastname = contact.familyName;
NSString *firstname = contact.givenName;
NSLog(@"%@ %@", lastname, firstname);
NSArray *phoneNums = contact.phoneNumbers;
for (CNLabeledValue *labeledValue in phoneNums) {
NSString *phoneLabel = labeledValue.label;
CNPhoneNumber *phoneNumer = labeledValue.value;
NSString *phoneValue = phoneNumer.stringValue;
NSLog(@"%@ %@", phoneLabel, phoneValue);
}
}];
}
四、寫入通訊錄
因為寫入的話這個功能有點重量級,寫入的時候要寫入,名字、電話、email、地址等等,這就會使得api過於複雜。暫時我見到過的做法大多都是如果用戶給了通訊錄許可權 那就給你插入一條名字+電話,我做了只有這兩個入參的api,當然使用時也完全可以擴展成更多參數的。
iOS7和8
- (void)creatItemWithName:(NSString *)name phone:(NSString *)phone
{
if((name.length < 1)||(phone.length < 1)){
NSLog(@"輸入屬性不能為空");
return;
}
CFErrorRef error = NULL;
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error);
ABRecordRef newRecord = ABPersonCreate();
ABRecordSetValue(newRecord, kABPersonFirstNameProperty, (__bridge CFTypeRef)name, &error);
ABMutableMultiValueRef multi = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(multi, (__bridge CFTypeRef)name, kABPersonPhoneMobileLabel, NULL);
ABRecordSetValue(newRecord, kABPersonPhoneProperty, multi, &error);
CFRelease(multi);
ABAddressBookAddRecord(addressBook, newRecord, &error);
ABAddressBookSave(addressBook, &error);
CFRelease(newRecord);
CFRelease(addressBook);
}
iOS9下
- (void)creatItemWithName:(NSString *)name phone:(NSString *)phone
{
// 創建對象
// 這個裡面可以添加多個電話,email,地址等等。 感覺使用率不高,只提供了最常用的屬性:姓名+電話,需要時可以自行擴展。
CNMutableContact * contact = [[CNMutableContact alloc]init];
contact.givenName = name?:@"defaultname";
CNLabeledValue *phoneNumber = [CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberMobile value:[CNPhoneNumber phoneNumberWithStringValue:phone?:@"10086"]];
contact.phoneNumbers = @[phoneNumber];
// 把對象加到請求中
CNSaveRequest * saveRequest = [[CNSaveRequest alloc]init];
[saveRequest addContact:contact toContainerWithIdentifier:nil];
// 執行請求
CNContactStore * store = [[CNContactStore alloc]init];
[store executeSaveRequest:saveRequest error:nil];
}
五、我的demo
因為不同版本用的類和枚舉都不一樣,所以我要設置一個統一的,並且在我的manager中處理各個版本間的判斷。 最後開放出來統一的api,只要引入頭文件SXAddressBookManager.h 就可以使用這些通用介面了。
①檢查當前狀態,有兩種api
- (void)checkStatus1 { SXAddressBookAuthStatus status = [[SXAddressBookManager manager]getAuthStatus]; if (status == kSXAddressBookAuthStatusNotDetermined) { [[SXAddressBookManager manager]askUserWithSuccess:^{ NSLog(@"點擊同意"); } failure:^{ NSLog(@"點擊拒絕"); }]; }else if (status == kSXAddressBookAuthStatusAuthorized){ NSLog(@"已有許可權"); }else{ NSLog(@"沒有許可權"); } }
- (void)checkStatus2 { [[SXAddressBookManager manager]checkStatusAndDoSomethingSuccess:^{ NSLog(@"已經有許可權,做相關操作,可以做讀取通訊錄等操作"); } failure:^{ NSLog(@"未得到許可權,做相關操作,可以做彈窗詢問等操作"); }]; }
②彈出選擇視窗,點擊回調選中的信息
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [[SXAddressBookManager manager]presentPageOnTarget:self chooseAction:^(SXPersonInfoEntity *person) { NSLog(@"%@---%@",person.fullname,person.phoneNumber); }]; }
③獲得整個通訊錄信息
self.personEntityArray = [[SXAddressBookManager manager]getPersonInfoArray];
④往通訊錄寫入一條信息
[[SXAddressBookManager manager]creatItemWithName:@"雷克薩斯-北京咨詢電話" phone:@"010-88657869"];
demo的地址是
https://github.com/dsxNiubility/SXEasyAddressBook
這裡寫了我說的那三點常用,如果以後有一些剛需,會不斷補充。 董鉑然博客園。