Contacts.framework是Apple在 iOS9.0 替代AddressBook.framework的框架爹梁,至于AddressBook是做什么的框架蚯斯,樓主默認看到博文的開發(fā)者是知道的 O(∩_∩)O妻率。
如果想了解AddressBook的使用歡迎查看一下樓主之前關(guān)于AddressBook的博文囊榜,本篇不做過多的綴余:
iOS開發(fā)------獲取系統(tǒng)聯(lián)系人(AddressBook篇)
iOS開發(fā)------操作通訊錄(AddressBook篇)&通訊錄UI(AddressBookUI篇)
每次iOS發(fā)布新的版本(甚至每年的WWDC大會舉行完畢)很多敏銳的開發(fā)者都準備或者對新版本特性進行適配舅柜。當然這些大神肯定會在iOS9發(fā)布后在第一時間對通訊錄功能進行適配瞒窒,一些稍微不太敏銳的開發(fā)者鑒于AddressBook在iOS9下初次提醒以及討厭適配的繁瑣,也就不以為然恋技。
但隨著iOS10的發(fā)布拇舀,那么適配相關(guān)框架就顯得格外重要(不是說AddressBook不能使用了,但為了項目的健壯性以及良好的體驗性蜻底,還是非常建議第一時間適配的骄崩。當然,這句話不僅限于Contacts部分)薄辅。
如果大家的項目還需要適配iOS8(當然要拂,大多數(shù)公司肯定是也不會拋棄iOS7的用戶),那么使用AddressBook是必然的站楚;但如果在iOS9+的系統(tǒng)上脱惰,樓主還是非常建議使用最新的Contacts.framework框架的.
個人推薦的主要是下面兩點原因(來源于樓主查看官方文檔,編寫Demo以及使用instruments的體會):
AddressBook與其他相關(guān)廢棄框架相似一樣 (ex:ALAsset-圖片庫)窿春,語言風格更接近于C語言(當然也可以說就是C語言)拉一,不在ARC管理之下(對于習慣使用ARC下的開發(fā)者算是不小的挑戰(zhàn))采盒,使用不太便利并容易造成內(nèi)存泄露。
新的框架無論在查看開發(fā)文檔舅踪、使用纽甘、讀取速度還是靈活性都遠好于廢棄框架良蛮,內(nèi)存泄露易于查找以及補漏抽碌。
這里還是要分享一下源碼,樓主整合AddressBook.framework以及Contacts.framework的DEMO
預覽圖
左邊為AddressBook框架進行的演示决瞳,右邊為Contact框架進行的演示.
根據(jù)不同的版本進行自動適配货徙,如果是iOS9,自動使用Contact.framework.
權(quán)限描述
在iOS10上由于權(quán)限有很多的坑皮胡,本博文的內(nèi)容需要使用通訊錄權(quán)限.
那么不要忘記在項目的info.plist文件中加入如下描述:Privacy - Contacts Usage Description
痴颊,描述字符串:RITL want to use your Contacts(這個隨意)
,盡可能的寫點東西吧屡贺,聽說如果不寫上線可能會被Apple拒絕..
獲取權(quán)限-CNContactStore
負責獲得權(quán)限蠢棱、請求權(quán)限以及執(zhí)行操作請求的類就是CNContactStore
,具體Demo中的代碼如下:
/**
檢測權(quán)限并作響應的操作
*/
- (void)__checkAuthorizationStatus
{
//這里有一個枚舉類:CNEntityType,不過沒關(guān)系甩栈,只有一個值:CNEntityTypeContacts
switch ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts])
{
//存在權(quán)限
case CNAuthorizationStatusAuthorized:
//獲取通訊錄
[self __obtainContacts:self.completeBlock];
break;
//權(quán)限未知
case CNAuthorizationStatusNotDetermined:
//請求權(quán)限
[self __requestAuthorizationStatus];break;
//如果沒有權(quán)限
case CNAuthorizationStatusRestricted:
case CNAuthorizationStatusDenied://需要提示
self.defendBlock();break;
}
}
請求聯(lián)系人列表-CNContactStore
這里有幾種比較常用的思路
1.使用自帶的枚舉方法一次性獲得所有的屬性
// 使用枚舉方法對所有的聯(lián)系人對象(CNContact)進行列出泻仙,該方法是同步的
- (BOOL)enumerateContactsWithFetchRequest:(CNContactFetchRequest *)fetchRequest
error:(NSError *__nullable *__nullable)error
usingBlock:(void (^)(CNContact *contact, BOOL *stop))block;
2.先獲取所有聯(lián)系人的identifier,再根據(jù)identifier讀取聯(lián)系人信息(Demo中使用的該思路)
// 通過identifer獲得一個唯一的CNContact
- (nullable CNContact *)unifiedContactWithIdentifier:(NSString *)identifier
keysToFetch:(NSArray<id<CNKeyDescriptor>> *)keys
error:(NSError *__nullable *__nullable)error;
遍歷請求類-CNContactFetchRequest
感覺這里介紹一下CNContactFetchRequest
類還是有必要的量没,畢竟當初在這里也是浪費了點時間玉转,它是一個遍歷請求的類,我們可以通過初始化該類的實例對象殴蹄,告訴contactStore我們需要遍歷contact的某些屬性:
//實例化CNContactFetchRequest對象,通過一個遍歷鍵的描述數(shù)組
- (instancetype)initWithKeysToFetch:(NSArray <id<CNKeyDescriptor>>*)keysToFetch NS_DESIGNATED_INITIALIZER;
鍵值描述協(xié)議-CNKeyDescriptor
如果我們單純的進入開發(fā)文檔究抓,我們會發(fā)現(xiàn)他是一個空協(xié)議,剛開始看到這里的時候樓主表示很蒙B
//沒有任何的required和optional方法
@protocol CNKeyDescriptor <NSObject, NSSecureCoding, NSCopying>
@end
但很快就發(fā)現(xiàn)了下面這個Category
// //Allows contact property keys to be used with keysToFetch.
// 允許contact的屬性鍵作為遍歷的鍵
@interface NSString (Contacts) <CNKeyDescriptor>
@end
如果還是有點疑惑袭灯,那么相信看到下面就不會再有困惑了呢刺下。沒錯,可以直接將下列字符串當成CNKeyDescriptor對象寫入數(shù)組
//標識符
CONTACTS_EXTERN NSString * const CNContactIdentifierKey NS_AVAILABLE(10_11, 9_0);
//姓名前綴
CONTACTS_EXTERN NSString * const CNContactNamePrefixKey NS_AVAILABLE(10_11, 9_0);
//姓名
CONTACTS_EXTERN NSString * const CNContactGivenNameKey NS_AVAILABLE(10_11, 9_0);
//中間名
CONTACTS_EXTERN NSString * const CNContactMiddleNameKey NS_AVAILABLE(10_11, 9_0);
//姓氏
CONTACTS_EXTERN NSString * const CNContactFamilyNameKey NS_AVAILABLE(10_11, 9_0);
//之前的姓氏(ex:國外的女士)
CONTACTS_EXTERN NSString * const CNContactPreviousFamilyNameKey NS_AVAILABLE(10_11, 9_0);
//姓名后綴
CONTACTS_EXTERN NSString * const CNContactNameSuffixKey NS_AVAILABLE(10_11, 9_0);
//昵稱
CONTACTS_EXTERN NSString * const CNContactNicknameKey NS_AVAILABLE(10_11, 9_0);
//公司(組織)
CONTACTS_EXTERN NSString * const CNContactOrganizationNameKey NS_AVAILABLE(10_11, 9_0);
//部門
CONTACTS_EXTERN NSString * const CNContactDepartmentNameKey NS_AVAILABLE(10_11, 9_0);
//職位
CONTACTS_EXTERN NSString * const CNContactJobTitleKey NS_AVAILABLE(10_11, 9_0);
//名字的拼音或音標
CONTACTS_EXTERN NSString * const CNContactPhoneticGivenNameKey NS_AVAILABLE(10_11, 9_0);
//中間名的拼音或音標
CONTACTS_EXTERN NSString * const CNContactPhoneticMiddleNameKey NS_AVAILABLE(10_11, 9_0);
//形式的拼音或音標
CONTACTS_EXTERN NSString * const CNContactPhoneticFamilyNameKey NS_AVAILABLE(10_11, 9_0);
//公司(組織)的拼音或音標(iOS10 才開始存在的呢)
CONTACTS_EXTERN NSString * const CNContactPhoneticOrganizationNameKey NS_AVAILABLE(10_12, 10_0);
//生日
CONTACTS_EXTERN NSString * const CNContactBirthdayKey NS_AVAILABLE(10_11, 9_0);
//農(nóng)歷
CONTACTS_EXTERN NSString * const CNContactNonGregorianBirthdayKey NS_AVAILABLE(10_11, 9_0);
//備注
CONTACTS_EXTERN NSString * const CNContactNoteKey NS_AVAILABLE(10_11, 9_0);
//頭像
CONTACTS_EXTERN NSString * const CNContactImageDataKey NS_AVAILABLE(10_11, 9_0);
//頭像的縮略圖
CONTACTS_EXTERN NSString * const CNContactThumbnailImageDataKey NS_AVAILABLE(10_11, 9_0);
//頭像是否可用
CONTACTS_EXTERN NSString * const CNContactImageDataAvailableKey NS_AVAILABLE(10_12, 9_0);
//類型
CONTACTS_EXTERN NSString * const CNContactTypeKey NS_AVAILABLE(10_11, 9_0);
//電話號碼
CONTACTS_EXTERN NSString * const CNContactPhoneNumbersKey NS_AVAILABLE(10_11, 9_0);
//郵箱地址
CONTACTS_EXTERN NSString * const CNContactEmailAddressesKey NS_AVAILABLE(10_11, 9_0);
//住址
CONTACTS_EXTERN NSString * const CNContactPostalAddressesKey NS_AVAILABLE(10_11, 9_0);
//其他日期
CONTACTS_EXTERN NSString * const CNContactDatesKey NS_AVAILABLE(10_11, 9_0);
//url地址
CONTACTS_EXTERN NSString * const CNContactUrlAddressesKey NS_AVAILABLE(10_11, 9_0);
//關(guān)聯(lián)人
CONTACTS_EXTERN NSString * const CNContactRelationsKey NS_AVAILABLE(10_11, 9_0);
//社交
CONTACTS_EXTERN NSString * const CNContactSocialProfilesKey NS_AVAILABLE(10_11, 9_0);
//即時通信
CONTACTS_EXTERN NSString * const CNContactInstantMessageAddressesKey NS_AVAILABLE(10_11, 9_0);
獲取聯(lián)系人姓名屬性
// RITLContactNameObject獲取姓名屬性的類目方法
-(void)contactObject:(CNContact *)contact
{
[super contactObject:contact];
//設置姓名屬性
self.nickName = contact.nickname; //昵稱
self.givenName = contact.givenName; //名字
self.familyName = contact.familyName; //姓氏
self.middleName = contact.middleName; //中間名
self.namePrefix = contact.namePrefix; //名字前綴
self.nameSuffix = contact.nameSuffix; //名字的后綴
self.phoneticGivenName = contact.phoneticGivenName; //名字的拼音或音標
self.phoneticFamilyName = contact.phoneticFamilyName;//姓氏的拼音或音標
self.phoneticMiddleName = contact.phoneticMiddleName;//中間名的拼音或音標
#ifdef __IPHONE_10_0
self.phoneticOrganizationName = contact.phoneticOrganizationName;//公司(組織)的拼音或音標
#endif
}
獲取聯(lián)系人的類型
這里需要判斷一下該屬性是否可用(不只該屬性稽荧,所有的屬性都應先判斷一下)不然會拋出異常.
/**
* 獲得聯(lián)系人類型信息
*/
+ (RITLContactType)__contactTypeProperty
{
if (![self.currentContact isKeyAvailable:CNContactTypeKey])
{
return RITLContactTypeUnknown;//沒有可用就是未知
}
else if (self.currentContact.contactType == CNContactTypeOrganization)
{
return RITLContactTypeOrigination;//如果是組織
}
else{
return RITLContactTypePerson;
}
}
獲得聯(lián)系人的頭像圖片
/**
* 獲得聯(lián)系人的頭像圖片
*/
+ (UIImage * __nullable)__contactHeadImagePropery
{
//縮略圖Data
if ([self.currentContact isKeyAvailable:CNContactThumbnailImageDataKey])
{
NSData * thumImageData = self.currentContact.thumbnailImageData;
return [UIImage imageWithData:thumImageData];
}
return nil;
}
獲取聯(lián)系人的電話信息
/**
* 獲得電話號碼對象數(shù)組
*/
+ (NSArray <RITLContactPhoneObject *> *)__contactPhoneProperty
{
if (![self.currentContact isKeyAvailable:CNContactPhoneNumbersKey])
{
return @[];
}
//外傳數(shù)組
NSMutableArray <RITLContactPhoneObject *> * phones = [NSMutableArray arrayWithCapacity:self.currentContact.phoneNumbers.count];
for (CNLabeledValue * phoneValue in self.currentContact.phoneNumbers)
{
//初始化PhoneObject對象
RITLContactPhoneObject * phoneObject = [RITLContactPhoneObject new];
//setValue
phoneObject.phoneTitle = [CNLabeledValue localizedStringForLabel:phoneValue.label];
phoneObject.phoneNumber = ((CNPhoneNumber *)phoneValue.value).stringValue;
[phones addObject:phoneObject];
}
return [NSArray arrayWithArray:phones];
}
獲取聯(lián)系人的工作信息
/**
* 獲得工作的相關(guān)屬性
*/
+ (RITLContactJobObject *)__contactJobProperty
{
RITLContactJobObject * jobObject = [[ RITLContactJobObject alloc]init];
if ([self.currentContact isKeyAvailable:CNContactJobTitleKey])
{
//setValue
jobObject.jobTitle = self.currentContact.jobTitle;
jobObject.departmentName = self.currentContact.departmentName;
jobObject.organizationName = self.currentContact.organizationName;
}
return jobObject;
}
獲取聯(lián)系人的郵件信息
/**
* 獲得Email對象的數(shù)組
*/
+ (NSArray <RITLContactEmailObject *> *)__contactEmailProperty
{
if (![self.currentContact isKeyAvailable:CNContactEmailAddressesKey])
{
return @[];
}
//外傳數(shù)組
NSMutableArray <RITLContactEmailObject *> * emails = [NSMutableArray arrayWithCapacity:self.currentContact.emailAddresses.count];
for (CNLabeledValue * emailValue in self.currentContact.emailAddresses)
{
//初始化RITLContactEmailObject對象
RITLContactEmailObject * emailObject = [[RITLContactEmailObject alloc]init];
//setValue
emailObject.emailTitle = [CNLabeledValue localizedStringForLabel:emailValue.label];
emailObject.emailAddress = emailValue.value;
[emails addObject:emailObject];
}
return [NSArray arrayWithArray:emails];
}
獲取聯(lián)系人的地址信息
/**
* 獲得Address對象的數(shù)組
*/
+ (NSArray <RITLContactAddressObject *> *)__contactAddressProperty
{
if (![self.currentContact isKeyAvailable:CNContactPostalAddressesKey]) {
return @[];
}
//外傳數(shù)組
NSMutableArray <RITLContactAddressObject *> * addresses = [NSMutableArray arrayWithCapacity:self.currentContact.postalAddresses.count];
for (CNLabeledValue * addressValue in self.currentContact.postalAddresses)
{
//初始化地址對象
RITLContactAddressObject * addressObject = [[RITLContactAddressObject alloc]init];
//setValues
addressObject.addressTitle = [CNLabeledValue localizedStringForLabel:addressValue.label];
//setDetailValue
[addressObject contactObject:addressValue.value];
//add object
[addresses addObject:addressObject];
}
return [NSArray arrayWithArray:addresses];
}
獲得聯(lián)系人的生日信息
/**
* 獲得生日的相關(guān)屬性
*/
+ (RITLContactBrithdayObject *)__contactBrithdayProperty
{
//實例化對象
RITLContactBrithdayObject * brithdayObject = [[RITLContactBrithdayObject alloc]init];
if ([self.currentContact isKeyAvailable:CNContactBirthdayKey])
{
//set value
brithdayObject.brithdayDate = [self.currentContact.birthday.calendar dateFromComponents:self.currentContact.birthday];
brithdayObject.leapMonth = self.currentContact.birthday.isLeapMonth;
}
if ([self.currentContact isKeyAvailable:CNContactNonGregorianBirthdayKey])
{
brithdayObject.calendar = self.currentContact.nonGregorianBirthday.calendar.calendarIdentifier;
brithdayObject.era = self.currentContact.nonGregorianBirthday.era;
brithdayObject.day = self.currentContact.nonGregorianBirthday.day;
brithdayObject.month = self.currentContact.nonGregorianBirthday.month;
brithdayObject.year = self.currentContact.nonGregorianBirthday.year;
}
//返回對象
return brithdayObject;
}
獲取聯(lián)系人的即時通信信息
/**
* 獲得即時通信賬號相關(guān)信息
*/
+ (NSArray <RITLContactInstantMessageObject *> *)__contactMessageProperty
{
if (![self.currentContact isKeyAvailable:CNContactInstantMessageAddressesKey])
{
return @[];
}
//存放數(shù)組
NSMutableArray <RITLContactInstantMessageObject *> * instantMessages = [NSMutableArray arrayWithCapacity:self.currentContact.instantMessageAddresses.count];
for (CNLabeledValue * instanceAddressValue in self.currentContact.instantMessageAddresses)
{
RITLContactInstantMessageObject * instaceObject = [[RITLContactInstantMessageObject alloc]init];
//set value
instaceObject.identifier = instanceAddressValue.identifier;
instaceObject.service = ((CNInstantMessageAddress *)instanceAddressValue.value).service;
instaceObject.userName = ((CNInstantMessageAddress *)instanceAddressValue.value).username;
//add
[instantMessages addObject:instaceObject];
}
return [NSArray arrayWithArray:instantMessages];
}
獲得聯(lián)系人的關(guān)聯(lián)人信息
/**
* 獲得聯(lián)系人的關(guān)聯(lián)人信息
*/
+ (NSArray <RITLContactRelatedNamesObject *> *)__contactRelatedNamesProperty
{
if (![self.currentContact isKeyAvailable:CNContactRelationsKey])
{
return @[];
}
//存放數(shù)組
NSMutableArray <RITLContactRelatedNamesObject *> * relatedNames = [NSMutableArray arrayWithCapacity:self.currentContact.contactRelations.count];
for (CNLabeledValue * relationsValue in self.currentContact.contactRelations)
{
RITLContactRelatedNamesObject * relatedObject = [[RITLContactRelatedNamesObject alloc]init];
//set value
relatedObject.identifier = relationsValue.identifier;
relatedObject.relatedTitle = [CNLabeledValue localizedStringForLabel:relationsValue.label];
relatedObject.relatedName = ((CNContactRelation *)relationsValue.value).name;
[relatedNames addObject:relatedObject];
}
return [NSArray arrayWithArray:relatedNames];
}
獲取聯(lián)系人的社交簡介信息
/**
* 獲得聯(lián)系人的社交簡介信息
*/
+ (NSArray <RITLContactSocialProfileObject *> *)__contactSocialProfilesProperty
{
if (![self.currentContact isKeyAvailable:CNContactSocialProfilesKey])
{
return @[];
}
//外傳數(shù)組
NSMutableArray <RITLContactSocialProfileObject *> * socialProfiles = [NSMutableArray arrayWithCapacity:self.currentContact.socialProfiles.count];
for (CNLabeledValue * socialProfileValue in self.currentContact.socialProfiles) {
RITLContactSocialProfileObject * socialProfileObject = [[RITLContactSocialProfileObject alloc]init];
//獲得CNSocialProfile對象
CNSocialProfile * socialProfile = socialProfileValue.value;
//set value
socialProfileObject.identifier = socialProfileValue.identifier;
socialProfileObject.socialProfileTitle = socialProfile.service;
socialProfileObject.socialProFileAccount = socialProfile.username;
socialProfileObject.socialProFileUrl = socialProfile.urlString;
[socialProfiles addObject:socialProfileObject];
}
return [NSArray arrayWithArray:socialProfiles];
}
獲取聯(lián)系人的備注信息
/**
獲得聯(lián)系人的備注信息
*/
+ (NSString * __nullable)__contactNoteProperty
{
if ([self.currentContact isKeyAvailable:CNContactNoteKey])
{
return self.currentContact.note;
}
return nil;
}
接收外界通訊錄發(fā)生變化的方法
這里不再是直接使用C語言的函數(shù)賦址來進行方法注冊怠李,方法更加的ObjC,選用了更多的通知中心蛤克。
/**
添加變化監(jiān)聽
*/
- (void)__addStoreDidChangeNotification
{
if (self.notificationDidAdd == false)
{
//添加通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(__contactDidChange:) name:CNContactStoreDidChangeNotification object:nil];
self.notificationDidAdd = !self.notificationDidAdd;
}
}
下面是執(zhí)行變化后的方法:
樓主的測試的時候通訊錄變化會連續(xù)觸發(fā)3次通知方法捺癞,后面的延遲3s就是解決連續(xù)觸發(fā)的問題,也不知道是個人的程序出問題還是Contact框架的bug构挤,如果大家有什么好辦法或者什么好的建議髓介,也請告知一下,十分感謝筋现。
/**
通訊錄發(fā)生變化進行的回調(diào)
@param notication 發(fā)送的通知
*/
- (void)__contactDidChange:(NSNotification *)notication
{
//重新獲取通訊錄
if (self.contactDidChange != nil )
{
//如果可以進行回調(diào)
if (self.shouldResponseContactChange == true)
{
//重新加載緩存
[[RITLContactCatcheManager sharedInstace]reloadContactIdentifiers:^(NSArray<NSString *> * _Nonnull identifiers) {
NSArray * contacts = [self __contactHandleWithIdentifiers:identifiers];
//回調(diào)
self.contactDidChange([contacts mutableCopy]);
}];
self.responseContactChange = false;
//延遲3s
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.responseContactChange = true;
});
}
}
}