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保持一致



<span style=           "font-size: 13px; font-family: 宋体;"           >    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           (@           "没有授权"           );          


                      // 弹窗提示去获取权限          


                      }</span>




iOS9及以后调用方法改成



<span style=           "font-size: 13px; font-family: 宋体;"           >     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           (@           "没有授权"           );          


                      }    </span>




二、弹出通讯录选择界面

iOS7的写法如下,代理方法的返回值大多是BOOL类型。


<span style=           "font-size: 13px; font-family: 宋体;"           >- (           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           ;          


           }          


           </span>




 

iOS8的代理方法换了,改成了下面两个,但是方法内部的取值基本相同



<span style=           "font-size: 13px; font-family: 宋体;"           >           // 点击了通讯录名字就会退出          


           - (           void           )peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person;          


                      


           // 点击了名字里面的电话或邮箱才会退出          


           - (           void           )peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier;          


           </span>




至于会调用哪一个方法,可以根据实际需要去选择,在弹出界面的方法中predicateForSelectionOfPerson 这个属性传false就是调用下面的。



<span style=           "font-size: 13px; font-family: 宋体;"           >    ABPeoplePickerNavigationController *pickervc = [[ABPeoplePickerNavigationController alloc] init];          


                      pickervc.predicateForSelectionOfPerson = [           NSPredicate            predicateWithValue:           false           ];          


                      pickervc.peoplePickerDelegate =            self           ;          


                      [target presentViewController:pickervc animated:           YES            completion:           nil           ];          


           </span>




 

iOS9系统下的弹出选择器方法 和 代理方法如下



<span style=           "font-size: 13px; font-family: 宋体;"           >            // 弹出选择器            


           - (           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);          


           }          


           </span>




这个是点击了名字就直接回调的方法,如果希望点击了属性再回调,则需要加上这一行



<span style=           "font-size: 13px; font-family: 宋体;"           >contactVc.predicateForSelectionOfContact = [           NSPredicate            predicateWithValue:           false           ];          


                      


           // 代理方法调用          


           - (           void           )contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty          


           </span>



 

三、获取全部通讯录信息

关于批量获取所有通讯录信息的方法有点冗长,这里就不一一贴了,只贴下iOS9的写法,iOS7和8的代码demo里都有。



<span style=           "font-size: 13px; font-family: 宋体;"           >- (           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);          


                      }          


                      }];          


           }          


           </span>



 

四、写入通讯录

因为写入的话这个功能有点重量级,写入的时候要写入,名字、电话、email、地址等等,这就会使得api过于复杂。暂时我见到过的做法大多都是如果用户给了通讯录权限 那就给你插入一条名字+电话,我做了只有这两个入参的api,当然使用时也完全可以扩展成更多参数的。

iOS7和8



<span style=           "font-size: 13px; font-family: 宋体;"           >- (           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);          


           }          


           </span>



iOS9下



<span style=           "font-size: 13px; font-family: 宋体;"           >- (           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           ];          


           }          


           </span>




 

五、我的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

这里写了我说的那三点常用,如果以后有一些刚需,会不断补充。