在工作中,很多時候會有讀取系統(tǒng)通訊錄的需求.尤其是一些IM相關(guān)的項目.
我們公司的幾個項目都是IM相關(guān)的,所以自然免不了這塊兒的內(nèi)容.用到的地方多了,所以就自己簡單地封裝了一個工具類來讀取.
首先,要實現(xiàn)的需求如下
這個界面布局是很簡單的.實現(xiàn)這個功能無非三步:
1.獲取通訊錄的聯(lián)系人數(shù)據(jù)
2.將獲取的聯(lián)系人數(shù)據(jù)上傳服務(wù)器(服務(wù)器要對每個聯(lián)系人進行判斷處理,以便實現(xiàn) 加好友 或 邀請下載APP的功能)
3.從服務(wù)器拉去聯(lián)系人數(shù)據(jù),根據(jù)拉去到的數(shù)據(jù)做顯示操作
可以看出這三步中,2.3是相對簡單的.比較復(fù)雜的就是第一步了,獲取系統(tǒng)的通訊錄聯(lián)系人數(shù)據(jù).那么,如何獲取系統(tǒng)通訊錄數(shù)據(jù)呢,接下來,就讓我們來仔細梳理一下iOS中如何獲取系統(tǒng)通訊錄吧.
首先,讀取系統(tǒng)通訊錄要導(dǎo)入與通訊錄相關(guān)的庫.鑒于ios9之前和之后與通訊錄相關(guān)的庫發(fā)生了改變,同時如果要兼容ios8的話,就得同時導(dǎo)入這兩個頭文件:
//ios9之前
#import <AddressBook/AddressBook.h>
//ios9之后
#import <ContactsUI/ContactsUI.h>
接下來呢,我們知道蘋果是非常注重用戶隱私的,所以程序在試圖訪問通訊錄時都得先經(jīng)過用戶的授權(quán),而系統(tǒng)的授權(quán)只有一次.那么如何獲取用戶授權(quán)呢,直接上代碼
#pragma mark---獲取授權(quán)---
- (void)getAuthoriseWithSuccess:(void (^)(void))success failed:(void (^)(void))failed {
if (TMiOS8) {
[self getAuthoriseBeforeIos_9WithSuccess:success failed:failed];
}else if(TMiOS9){
[self getAuthoriseAfterIos_9WithSuccess:success failed:failed];
}
}
#pragma mark---ios9之前獲取授權(quán)---
- (void)getAuthoriseBeforeIos_9WithSuccess:(void (^)(void))success failed:(void (^)(void))failed {
//獲取授權(quán)狀態(tài)
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
if (status == kABAuthorizationStatusNotDetermined) {
//The user has not yet made a choice regarding whether this app can access the data class.(用戶尚未作出選擇封字,關(guān)于此應(yīng)用程序是否可以訪問數(shù)據(jù)類。)
ABAddressBookRef book = ABAddressBookCreateWithOptions(NULL, NULL);
ABAddressBookRequestAccessWithCompletion(book, ^(bool granted, CFErrorRef error) {
if (granted) {
TMLog(@"授權(quán)成功!");
GCDMainBlock(success);
}else{
TMLog(@"授權(quán)失敗!");
CGDMainBack;
}
});
}
else if (status == kABAuthorizationStatusAuthorized) {
//This application is authorized to access the data class. (此應(yīng)用程序被授權(quán)訪問數(shù)據(jù)類。)
GCDMainBlock(success);
}
else if (status == kABAuthorizationStatusDenied) {
//The user explicitly denied access to the data class for this application.(用戶明確拒絕此應(yīng)用程序訪問數(shù)據(jù)類阔籽。)
GCDMainBlock(failed);
}
else if (status == kABAuthorizationStatusRestricted) {
//This application is not authorized to access the data class. The user cannot change this application’s status, possibly due to active restrictions such as parental controls being in place.(此應(yīng)用程序未授權(quán)訪問數(shù)據(jù)類流妻。用戶不能更改此應(yīng)用程序的狀態(tài),可能是由于活動限制)
GCDMainBlock(failed);
}
}
#pragma mark---ios9之后獲取通訊錄授權(quán)---
- (void)getAuthoriseAfterIos_9WithSuccess:(void (^)(void))success failed:(void (^)(void))failed {
//獲取授權(quán)狀態(tài)
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if (status == CNAuthorizationStatusNotDetermined) {
//The user has not yet made a choice regarding whether the application may access contact data.(還未對應(yīng)用程序進行過獲取聯(lián)系人授權(quán)的選擇) 請求系統(tǒng)授權(quán)(系統(tǒng)授權(quán)彈框僅有一次)
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
TMLog(@"授權(quán)成功!");
GCDMainBlock(success);
}else{
TMLog(@"授權(quán)失敗!");
CGDMainBack;
}
}];
}else if (status == CNAuthorizationStatusAuthorized) {
//The application is authorized to access contact data.(用戶授權(quán)應(yīng)用程序訪問聯(lián)系人數(shù)據(jù))
GCDMainBlock(success);
}else if (status == CNAuthorizationStatusDenied) {
//The user explicitly denied access to contact data for the application.(用戶明確拒絕應(yīng)用程序訪問聯(lián)系人數(shù)據(jù)笆制。)
GCDMainBlock(failed);
}else if (status == CNAuthorizationStatusRestricted) {
//The application is not authorized to access contact data.The user cannot change this application’s status, possibly due to active restrictions such as parental controls being in place.(應(yīng)用程序沒有權(quán)限訪問聯(lián)系人數(shù)據(jù).并且用戶無法更改應(yīng)用程序的權(quán)限狀態(tài),可能是由于某種活動限制)
GCDMainBlock(failed);
}
}
兩個block參數(shù)分別是判斷系統(tǒng)授權(quán)成功與否的回調(diào).當判斷授權(quán)成功時調(diào)用success,授權(quán)失敗時調(diào)用faild.
這里有個問題,可以發(fā)現(xiàn)我在調(diào)用block的時候,套用了事先用宏定義的方法,為什么呢?是因為在程序調(diào)試過程中我發(fā)現(xiàn)明明已經(jīng)授權(quán)成功了,但是卻遲遲不能完成界面跳轉(zhuǎn),后來找來公司同事幫忙找bug,發(fā)現(xiàn)獲取授權(quán)是新開了異步線程,而授權(quán)成功之后遲遲沒有響應(yīng)則是因為新開的異步線程還未銷毀,所以這里就手動調(diào)用方法讓它回到主線程(我也不知道這樣說對不對,有哪里不對的地方還請指正~手動比心)
#pragma mark - GCD Block
#define GCDBlock(block) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block)
#define GCDMainBlock(block) dispatch_async(dispatch_get_main_queue(),block)
#define CGDMainBack GCDMainBlock(^(){})
完成這一步之后,關(guān)于系統(tǒng)通訊錄的授權(quán)部分我們就已經(jīng)可以實現(xiàn)了.只要用該封裝好的單例類調(diào)用獲取授權(quán)的方法就好了.
在以上添加好友界面點擊"手機通訊錄中的朋友"時寫如下代碼即可
[[TMAddressBookTool shareTool] getAuthoriseWithSuccess:^{
//授權(quán)成功
//跳轉(zhuǎn)界面
[self openViewControllerByPush:[[SFAddressBookTableVC alloc] init]];
} failed:^{
//授權(quán)失敗 提示授權(quán)失敗 同時跳轉(zhuǎn)到設(shè)置界面更改權(quán)限
[self showAlertWithTitle:@"提示" message:@"請您先去\n手機設(shè)置權(quán)限管理里面\n修改權(quán)限" confirmTitle:@"確定" isCancel:NO queding:^{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
} cancel:nil];
}];
如果是第一次授權(quán),那么會彈出如下彈框
點擊"不允許"時彈框消失,不再有后續(xù)反應(yīng).此時如若再次點擊"手機通訊錄中的朋友",就會走失敗回調(diào),給用戶比較友好的提示,如下圖所示,我們公司的需求是點擊彈框中的"確定"按鈕后跳轉(zhuǎn)到設(shè)置界面以便讓用戶更改授權(quán)狀態(tài).
點擊"好"之后會回調(diào)授權(quán)成功的block,進行頁面跳轉(zhuǎn).
哈哈哈哈,我剛換了手機,通訊錄什么的沒導(dǎo)進來,也就是說我的手機通訊錄里面什么都沒有哈.接下來我們談?wù)勗谶@個界面要做哪些操作呢.其實這個界面要做的在文章一開始就已經(jīng)說過了.
1.獲取通訊錄的聯(lián)系人數(shù)據(jù)
2.將獲取的聯(lián)系人數(shù)據(jù)上傳服務(wù)器(服務(wù)器要對每個聯(lián)系人進行判斷處理,以便實現(xiàn) 加好友 或 邀請下載APP的功能)
3.從服務(wù)器拉去聯(lián)系人數(shù)據(jù),根據(jù)拉去到的數(shù)據(jù)做顯示操作
我們重點來說第一點,之前寫的那一堆內(nèi)容都是第一點的鋪墊.下面開始介紹如何獲取通訊錄的聯(lián)系人數(shù)據(jù).
#pragma mark---讀取通訊錄---
//返回聯(lián)系人數(shù)組 并將聯(lián)系人數(shù)組轉(zhuǎn)化為json字符串
- (NSString *)readAddressBook {
NSArray *addressBook = [NSArray array];
if (TMiOS8) {
addressBook = [self readAddressBookBeforeIos_9];
}else if(TMiOS9) {
addressBook = [self readAddressBookAfterIos_9];
}
//將通訊錄跟本地記錄做比較并且保存
return [self getJsonStr:addressBook.copy];
}
#pragma mark---讀取通訊錄 ios9之前---
- (NSArray *)readAddressBookBeforeIos_9 {
NSMutableArray *addressBook = [[NSMutableArray alloc] init];
//創(chuàng)建一個通訊錄實例
ABAddressBookRef book = ABAddressBookCreateWithOptions(NULL, NULL);
//獲取聯(lián)系人總數(shù)
CFArrayRef allpeople = ABAddressBookCopyArrayOfAllPeople(book);
CFIndex count = CFArrayGetCount(allpeople);
//遍歷
for (CFIndex i = 0; i <count ; i++) {
//獲取聯(lián)系人對象的引用
ABRecordRef record = CFArrayGetValueAtIndex(allpeople, i);
//獲取當前聯(lián)系人姓氏
CFStringRef strLast = ABRecordCopyValue(record, kABPersonLastNameProperty);
NSString *lastName = (__bridge_transfer NSString *)strLast;
NSString *name = lastName;
//創(chuàng)建一個可變數(shù)組用來存放該聯(lián)系人的電話號碼(同一個聯(lián)系人的電話號碼不止一個)
NSMutableArray *phoneArr = [NSMutableArray array];
//獲取當前聯(lián)系人的電話(數(shù)組)
ABMultiValueRef multivalue = ABRecordCopyValue(record, kABPersonPhoneProperty);
//遍歷電話數(shù)組
for (CFIndex i = 0; i < ABMultiValueGetCount(multivalue); i++) {
//獲取電話號碼 并轉(zhuǎn)換成NSString類型
CFStringRef phoneStr = ABMultiValueCopyValueAtIndex(multivalue, i);
NSString *phone = (__bridge_transfer NSString *)(phoneStr);
//判斷手機號碼同時將其加入到事先創(chuàng)建好的手機號碼數(shù)組
[self judgePhoneNumberWithPhoneArray:phoneArr phoneNum:phone name:name];
}
//當電話數(shù)組不為空時,以字典形式包裝聯(lián)系人名稱和電話數(shù)組(我們跟后臺定好的字段是"memo"/"mobile",這里具體按你們自己的字段為準),同時將字典添加到預(yù)先創(chuàng)建的可變數(shù)組中
[self judgePhoneArray:phoneArr contactsArray:addressBook name:name];
}
return addressBook.copy;
}
#pragma mark---讀取通訊錄 ios9之后---
- (NSArray *)readAddressBookAfterIos_9 {
//初始化一個可變數(shù)組,用來存放遍歷到的所有聯(lián)系人
NSMutableArray *addressBook = [[NSMutableArray alloc] init];
//創(chuàng)建通訊錄對象
CNContactStore *store = [[CNContactStore alloc] init];
//定義所有打算獲取的屬性對應(yīng)的key值,這里我們要獲取姓名/姓氏/以及手機號碼
NSArray *keys = @[CNContactGivenNameKey,CNContactFamilyNameKey,CNContactPhoneNumbersKey];
//創(chuàng)建CNContactFetchRequest對象
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
//遍歷所有的聯(lián)系人
[store enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
//拿到當前聯(lián)系人的姓名
NSString *name = [NSString stringWithFormat:@"%@%@",contact.familyName,contact.givenName];
//初始化一個可變數(shù)組,用來存放手機號碼(同一個聯(lián)系人可能對用多個手機號)
NSMutableArray *phoneArr = [NSMutableArray array];
//遍歷當前聯(lián)系人的號碼數(shù)組
for (CNLabeledValue * objc in contact.phoneNumbers
) {
//獲取當前手機號碼
CNPhoneNumber *num = objc.value;
NSString *phoneStr = num.stringValue;
//處理手機號碼字符串 過濾掉"+","(",")","-"等多余字符(便于后臺處理數(shù)據(jù))
phoneStr = [self getStandardPhoneNum:phoneStr];
//判斷手機號碼同時將其加入到事先創(chuàng)建好的手機號碼數(shù)組
[self judgePhoneNumberWithPhoneArray:phoneArr phoneNum:phoneStr name:name];
}
//當電話數(shù)組不為空時,以字典形式包裝聯(lián)系人名稱和電話數(shù)組(我們跟后臺定好的字段是"memo"/"mobile",這里具體按你們自己的字段為準),同時將字典添加到預(yù)先創(chuàng)建的可變數(shù)組中
[self judgePhoneArray:phoneArr contactsArray:addressBook name:name];
}];
return addressBook.copy;
}
#pragma mark---判斷手機號碼同時將其加入到事先創(chuàng)建好的手機號碼數(shù)組---
- (void)judgePhoneNumberWithPhoneArray:(NSMutableArray *)phoneArray phoneNum:(NSString *)phone name:(NSString *)name {
//當前聯(lián)系人的名字為空但是電話不為空時 默認將名字設(shè)為電話號碼(我們公司是這樣做的)
if (phone.length > 0 && name.length == 0) {
name = phone;
}
//當電話不為空時 將當前電話號碼添加到電話數(shù)組中
if (phone.length > 0) {
[phoneArray addObject:phone];
}
}
#pragma mark---當電話數(shù)組不為空時,以字典形式包裝聯(lián)系人名稱和電話數(shù)組,同時將字典添加到預(yù)先創(chuàng)建的可變數(shù)組中---
- (void)judgePhoneArray:(NSMutableArray *)phoneArray contactsArray:(NSMutableArray *)contactsArray name:(NSString *)name {
if (phoneArray.count > 0) {
//(我們跟后臺定好的字段是"memo"/"mobile",這里具體按你們自己的字段為準)
NSDictionary *contactInfo = @{@"mobile": phoneArray,@"memo": name};
[contactsArray addObject:contactInfo];
}
}
#pragma mark---將聯(lián)系人數(shù)組轉(zhuǎn)換成json字符串---
- (NSString *)getJsonStr:(NSArray *)contacts {
NSData *data = [NSJSONSerialization dataWithJSONObject:contacts options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
jsonStr = [jsonStr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
jsonStr = [jsonStr stringByReplacingOccurrencesOfString:@" " withString:@""];
return jsonStr;
}
#pragma mark---處理電話號碼---
- (NSString *)getStandardPhoneNum: (NSString *)phoneStr {
phoneStr = [phoneStr stringByReplacingOccurrencesOfString:@"-" withString:@""];
phoneStr = [phoneStr stringByReplacingOccurrencesOfString:@"(" withString:@""];
phoneStr = [phoneStr stringByReplacingOccurrencesOfString:@")" withString:@""];
phoneStr = [phoneStr stringByReplacingOccurrencesOfString:@" " withString:@""];
phoneStr = [phoneStr stringByReplacingOccurrencesOfString:@"+" withString:@""];
return phoneStr;
}
也就是說在"手機通訊錄"界面寫如下代碼就可以拿到你的通訊錄的所有聯(lián)系人了
//這里你拿到的addressBookStr就是通訊錄的所有聯(lián)系人的json字符串
NSString *addressBookStr = [[SFAddressBookTool shareTool] readAddressBook];
下一步就可以上傳通訊錄了,上傳成功之后再調(diào)用獲取通訊錄的接口,這樣就能從后臺拿到關(guān)于你的手機通訊錄中的某個人是否注冊過該應(yīng)用以便于做對應(yīng)的顯示.