iOS通訊錄整合,兼容iOS789寫法,附demo


蘋果的通訊錄功能在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

這里寫了我說的那三點常用,如果以后有一些剛需,會不斷補充。 董鉑然博客園。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM