iOS中如何自定義手機通訊錄

在工作中,很多時候會有讀取系統(tǒng)通訊錄的需求.尤其是一些IM相關(guān)的項目.

我們公司的幾個項目都是IM相關(guān)的,所以自然免不了這塊兒的內(nèi)容.用到的地方多了,所以就自己簡單地封裝了一個工具類來讀取.

首先,要實現(xiàn)的需求如下


屏幕快照 2017-02-16 下午8.25.56.png

這個界面布局是很簡單的.實現(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)的方法就好了.


IMG_0038.PNG

在以上添加好友界面點擊"手機通訊錄中的朋友"時寫如下代碼即可

    [[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),那么會彈出如下彈框


IMG_0039.JPG

點擊"不允許"時彈框消失,不再有后續(xù)反應(yīng).此時如若再次點擊"手機通訊錄中的朋友",就會走失敗回調(diào),給用戶比較友好的提示,如下圖所示,我們公司的需求是點擊彈框中的"確定"按鈕后跳轉(zhuǎn)到設(shè)置界面以便讓用戶更改授權(quán)狀態(tài).


IMG_0037.PNG

點擊"好"之后會回調(diào)授權(quán)成功的block,進行頁面跳轉(zhuǎn).

IMG_0040.PNG

哈哈哈哈,我剛換了手機,通訊錄什么的沒導(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)的顯示.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绅这,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子在辆,更是在濱河造成了極大的恐慌证薇,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匆篓,死亡現(xiàn)場離奇詭異浑度,居然都是意外死亡,警方通過查閱死者的電腦和手機鸦概,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門箩张,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窗市,你說我怎么就攤上這事先慷。” “怎么了咨察?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵论熙,是天一觀的道長。 經(jīng)常有香客問我扎拣,道長赴肚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任二蓝,我火速辦了婚禮誉券,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刊愚。我一直安慰自己踊跟,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布鸥诽。 她就那樣靜靜地躺著商玫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牡借。 梳的紋絲不亂的頭發(fā)上拳昌,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音钠龙,去河邊找鬼炬藤。 笑死御铃,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的沈矿。 我是一名探鬼主播上真,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼羹膳!你這毒婦竟也來了睡互?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤陵像,失蹤者是張志新(化名)和其女友劉穎就珠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蠢壹,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡嗓违,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年九巡,在試婚紗的時候發(fā)現(xiàn)自己被綠了图贸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡冕广,死狀恐怖疏日,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情撒汉,我是刑警寧澤沟优,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站睬辐,受9級特大地震影響挠阁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜溯饵,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一侵俗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丰刊,春花似錦隘谣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至秩仆,卻和暖如春码泛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背澄耍。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工噪珊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忘衍,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓卿城,卻偏偏與公主長得像枚钓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瑟押,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容