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保持一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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及以后调用方法改成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | - ( 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的代理方法换了,改成了下面两个,但是方法内部的取值基本相同
1 2 3 4 5 | // 点击了通讯录名字就会退出 - ( void )peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person; // 点击了名字里面的电话或邮箱才会退出 - ( void )peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier; |
至于会调用哪一个方法,可以根据实际需要去选择,在弹出界面的方法中predicateForSelectionOfPerson 这个属性传false就是调用下面的。
1 2 3 4 | ABPeoplePickerNavigationController *pickervc = [[ABPeoplePickerNavigationController alloc] init]; pickervc.predicateForSelectionOfPerson = [ NSPredicate predicateWithValue: false ]; pickervc.peoplePickerDelegate = self ; [target presentViewController:pickervc animated: YES completion: nil ]; |
iOS9系统下的弹出选择器方法 和 代理方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | // 弹出选择器 - ( 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); } |
这个是点击了名字就直接回调的方法,如果希望点击了属性再回调,则需要加上这一行
1 2 3 4 | contactVc.predicateForSelectionOfContact = [ NSPredicate predicateWithValue: false ]; // 代理方法调用 - ( void )contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty |
三、获取全部通讯录信息
关于批量获取所有通讯录信息的方法有点冗长,这里就不一一贴了,只贴下iOS9的写法,iOS7和8的代码demo里都有。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | - ( 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | - ( 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下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | - ( 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | - ( void )checkStatus1 { SXAddressBookAuthStatus status = [[SXAddressBookManager manager]getAuthStatus]; if (status == kSXAddressBookAuthStatusNotDetermined) { [[SXAddressBookManager manager]askUserWithSuccess:^{ NSLog (@ "点击同意" ); } failure:^{ NSLog (@ "点击拒绝" ); }]; } else if (status == kSXAddressBookAuthStatusAuthorized){ NSLog (@ "已有权限" ); } else { NSLog (@ "没有权限" ); } } |
1 2 3 4 5 6 7 8 | - ( void )checkStatus2 { [[SXAddressBookManager manager]checkStatusAndDoSomethingSuccess:^{ NSLog (@ "已经有权限,做相关操作,可以做读取通讯录等操作" ); } failure:^{ NSLog (@ "未得到权限,做相关操作,可以做弹窗询问等操作" ); }]; } |
②弹出选择窗口,点击回调选中的信息
1 2 3 4 5 6 | - ( void )touchesBegan:( NSSet <UITouch *> *)touches withEvent:(UIEvent *)event { [[SXAddressBookManager manager]presentPageOnTarget: self chooseAction:^(SXPersonInfoEntity *person) { NSLog (@ "%@---%@" ,person.fullname,person.phoneNumber); }]; } |
③获得整个通讯录信息
1 | self .personEntityArray = [[SXAddressBookManager manager]getPersonInfoArray]; |
④往通讯录写入一条信息
1 | [[SXAddressBookManager manager]creatItemWithName:@ "雷克萨斯-北京咨询电话" phone:@ "010-88657869" ]; |
demo的地址是
https://github.com/dsxNiubility/SXEasyAddressBook
这里写了我说的那三点常用,如果以后有一些刚需,会不断补充。 董铂然博客园。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步