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