ios 獲取通訊錄的方式

獲取通訊錄的方式

  • iOS9以前 使用 AddressBookUI.framework,AddressBook.framework
  • iOS9以后 使用 ContactsUI.framework,Contacts.framework
    簡單的說明下,其中 AddressBookUI 和 ContactsUI 是彈出一個通訊錄界面提供選擇一條聯(lián)系人信息并且是不需要手動授權(quán), AddressBook 和 Contacts 是獲取全部通訊錄數(shù)據(jù)并且需要手動授權(quán)
    注意在iOS10獲取通訊錄權(quán)限需主動在info.plist里添加上提示信息. 不然會崩潰. 在info.plistkey: Privacy - Contacts Usage Descriptionvalue:是否允許此App訪問你的通訊錄

聯(lián)系人模型 ContactModel

ContactModel.h

#import <Foundation/Foundation.h>

@interface ContactModel : NSObject
/** num */
@property (nonatomic, copy) NSString *num;
/** 姓名 */
@property (nonatomic, copy) NSString *name;

- (instancetype)initWithName:(NSString *)name phoneNum:(NSString *)num;

@end

ContactModel.m

#import "ContactModel.h"

@implementation ContactModel

- (instancetype)initWithName:(NSString *)name phoneNum:(NSString *)num
{
    if (self = [super init]) {
        self.name = name;
        self.num = num;
    }
    
    return self;
}


@end

工具類 ContactModel

ContactsTool.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#import "ContactModel.h"
typedef void(^ContactBlock)(ContactModel *contactsModel);

@interface ContactsTool : NSObject
+ (NSMutableArray *)getAllPhoneInfo;

- (void)getOnePhoneInfoWithUI:(UIViewController *)target callBack:(ContactBlock)block;

@end

ContactsTool.m

#import "ContactsTool.h"
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
#import <Contacts/Contacts.h>
#import <ContactsUI/ContactsUI.h>

#define iOS9Later ([UIDevice currentDevice].systemVersion.floatValue >= 9.0f)


@interface ContactsTool ()<CNContactPickerDelegate, ABPeoplePickerNavigationControllerDelegate>
@property(nonatomic, strong) ContactModel *contactModel;
@property(nonatomic, copy) ContactBlock myBlock;
@end

@implementation ContactsTool
+ (NSMutableArray *)getAllPhoneInfo {
    return iOS9Later ? [self getContactsFromContactsAll] : [self getContactsFromAddressBookAll];
}

- (void)getOnePhoneInfoWithUI:(UIViewController *)target callBack:(ContactBlock)block
{
    if (iOS9Later) {
        [self getContactsFromContactUI:target];
    } else {
        [self getContactsFromAddressBookUI:target];
    }
    self.myBlock = block;
}

#pragma mark - AddressBookUI
- (void)getContactsFromAddressBookUI:(UIViewController *)target {
    ABPeoplePickerNavigationController *pickerVC = [[ABPeoplePickerNavigationController alloc] init];
    pickerVC.peoplePickerDelegate = self;
    [target presentViewController:pickerVC animated:YES completion:nil];
}

/**
 *  當用戶選擇某一個聯(lián)系人的某一個屬性的時候會執(zhí)行該方法
 *
 *  @param person       選中的聯(lián)系人
 *  @param property     選中的聯(lián)系人的屬性
 *  @param identifier   每一個屬性都有一個對應的表示
 */

- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person {
    
    
    // 將CoreFoundation框架的對象轉(zhuǎn)成Foundation框架對象,那么可以通過橋接的方式
    // 如果是CoreFoundation框架中的對象,如果是通過copy或者create或者retain,必須對應有一個release
    /*
     __bridge type: 通過該橋接方式,那么CoreFoundation對應的對象需要手動來釋放,Foundation框架的對象如果是在ARC環(huán)境下面,則不需手動釋放
     __bridge_transfer type: 通過該橋接方式,那么CoreFoundation對應的對象表示已經(jīng)交給Foundation對象進行管理,如果是在ARC環(huán)境下面,不需要釋放任何一個對象
     */
    
    ABMultiValueRef phonesRef = ABRecordCopyValue(person, kABPersonPhoneProperty);
    if (!phonesRef) { return; }
    NSString *phoneValue = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phonesRef, 0);
    
    CFStringRef lastNameRef = ABRecordCopyValue(person, kABPersonLastNameProperty);
    CFStringRef firstNameRef = ABRecordCopyValue(person, kABPersonFirstNameProperty);
    NSString *lastname = (__bridge_transfer NSString *)(lastNameRef);
    NSString *firstname = (__bridge_transfer NSString *)(firstNameRef);
    NSString *name = [NSString stringWithFormat:@"%@%@", lastname == NULL ? @"" : lastname, firstname == NULL ? @"" : firstname];
    NSLog(@"姓名: %@", name);
    
    ContactModel *model = [[ContactModel alloc] initWithName:name phoneNum:phoneValue];
    NSLog(@"電話號碼: %@", phoneValue);
    
    CFRelease(phonesRef);
    if (self.myBlock) self.myBlock(model);
}

#pragma mark - ContactsUI
- (void)getContactsFromContactUI:(UIViewController *)target {
    CNContactPickerViewController *pickerVC = [[CNContactPickerViewController alloc] init];
    pickerVC.delegate = self;
    [target presentViewController:pickerVC animated:YES completion:nil];
}

- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact {
    NSString *name = [NSString stringWithFormat:@"%@%@", contact.familyName == NULL ? @"" : contact.familyName, contact.givenName == NULL ? @"" : contact.givenName];
    NSLog(@"姓名: %@", name);
    
    CNPhoneNumber *phoneNumber = [contact.phoneNumbers[0] value];
    ContactModel *model = [[ContactModel alloc] initWithName:name phoneNum:[NSString stringWithFormat:@"%@", phoneNumber.stringValue]];
    NSLog(@"電話號碼: %@", phoneNumber.stringValue);
    
    if (self.myBlock) self.myBlock(model);
}

#pragma mark - AddressBook
+ (NSMutableArray *)getContactsFromAddressBookAll {
    //取得通訊錄訪問授權(quán)狀態(tài)
    ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
    CFErrorRef myError = NULL;
    // ABAddressBookRef 代表通訊對象
    //調(diào)用ABAddressBookCreateWithOptions()方法創(chuàng)建通訊錄對象ABAddressBookRef
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &myError);
    if (myError) {
        [self showErrorAlert];
        if (addressBook) CFRelease(addressBook);
        return nil;
    }
    
    /*
     
     kABAuthorizationStatusNotDetermined = 0, 沒有決定是否授權(quán)
     
     kABAuthorizationStatusRestricted,  受限制
     
     kABAuthorizationStatusDenied,  拒絕
     
     kABAuthorizationStatusAuthorized  授權(quán)
     
     */
    
    __block NSMutableArray *contactModels = [NSMutableArray array];
    if (status == kABAuthorizationStatusNotDetermined) {  // 用戶還沒有決定是否授權(quán)你的程序進行訪問
        ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
            if (granted) { //授權(quán)成功
                contactModels = [self getAddressBookInfo:addressBook];
            } else {
                [self showErrorAlert];
                if (addressBook) CFRelease(addressBook);
            }
        });
        // 用戶已拒絕 或 iOS設備上的家長控制或其它一些許可配置阻止程序與通訊錄數(shù)據(jù)庫進行交互
    } else if (status == kABAuthorizationStatusDenied || status == kABAuthorizationStatusRestricted) {
        [self showErrorAlert];
        if (addressBook) CFRelease(addressBook);
    } else if (status == kABAuthorizationStatusAuthorized) {  // 用戶已授權(quán)
        contactModels = [self getAddressBookInfo:addressBook];
    }
    return contactModels;
}

/*
 
 橋接有三種方式:
 
 (__bridge type)(expression) : 只是讓NSFoundation框架暫時使用CF框架對象,注意需要手動釋放 Core Foundation 對象,用CFRelease( )函數(shù)。
 (__bridge_transfer type)(expression) / CFBridgingRelease(expression) : CF框架移交對象的管理權(quán)給NSFoundation框架,不需要手動釋放對象
 前兩種是將CF對象轉(zhuǎn)NSFoundation,最后一個是NSFoundation轉(zhuǎn) CF對象,不常用
 (__bridge_retained )()
  
 */

+ (NSMutableArray *)getAddressBookInfo:(ABAddressBookRef)addressBook {
    CFArrayRef peopleArray = ABAddressBookCopyArrayOfAllPeople(addressBook);
    NSInteger peopleCount = CFArrayGetCount(peopleArray);
    NSMutableArray *contactModels = [NSMutableArray array];
    
    for (int i = 0; i < peopleCount; i++) {
        ABRecordRef person = CFArrayGetValueAtIndex(peopleArray, i);
        ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
        if (phones) {
            NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
            NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
            NSString *name = [NSString stringWithFormat:@"%@%@", lastName == NULL ? @"" : lastName, firstName == NULL ? @"" : firstName];
            NSLog(@"姓名: %@", name);
            
            CFIndex phoneCount = ABMultiValueGetCount(phones);
            for (int j = 0; j < phoneCount; j++) {
                NSString *phoneValue = (__bridge_transfer NSString *)ABMultiValueCopyValueAtIndex(phones, j);
                NSLog(@"電話號碼: %@", phoneValue);
                ContactModel *model = [[ContactModel alloc] initWithName:name phoneNum:phoneValue];
                [contactModels addObject:model];
            }
        }
        CFRelease(phones);
    }
    
    if (addressBook) CFRelease(addressBook);
    if (peopleArray) CFRelease(peopleArray);
    
    return contactModels;
}


#pragma mark - Contacts
+ (NSMutableArray *)getContactsFromContactsAll {
    CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
    CNContactStore *store = [[CNContactStore alloc] init];
    __block NSMutableArray *contactModels = [NSMutableArray array];
    
    if (status == CNAuthorizationStatusNotDetermined) { // 用戶還沒有決定是否授權(quán)你的程序進行訪問
        [store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted) {
                contactModels = [self getContactsInfo:store];
            } else {
                [self showErrorAlert];
            }
        }];
        // 用戶已拒絕 或 iOS設備上的家長控制或其它一些許可配置阻止程序與通訊錄數(shù)據(jù)庫進行交互
    } else if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {
        [self showErrorAlert];
    } else if (status == CNAuthorizationStatusAuthorized) { // 用戶已授權(quán)
        contactModels = [self getContactsInfo:store];
    }
    
    return contactModels;
}

+ (NSMutableArray *)getContactsInfo:(CNContactStore *)store {
    NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
    CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];
    NSMutableArray *contactModels = [NSMutableArray array];
    
    [store enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
        NSString *name = [NSString stringWithFormat:@"%@%@", contact.familyName == NULL ? @"" : contact.familyName, contact.givenName == NULL ? @"" : contact.givenName];
        NSLog(@"姓名: %@", name);
        
        for (CNLabeledValue *labeledValue in contact.phoneNumbers) {
            CNPhoneNumber *phoneNumber = labeledValue.value;
            NSLog(@"電話號碼: %@", phoneNumber.stringValue);
            ContactModel *model = [[ContactModel alloc] initWithName:name phoneNum:phoneNumber.stringValue];
            [contactModels addObject:model];
        }
    }];
    
    return contactModels;
}

#pragma mark - Error
+ (void)showErrorAlert {
    NSLog(@"授權(quán)失敗, 請允許app訪問您的通訊錄, 在手機的”設置-隱私-通訊錄“選項中設置允許");
}

@end

tips
將CoreFoundation框架的對象轉(zhuǎn)成Foundation框架對象,那么可以通過橋接的方式
如果是CoreFoundation框架中的對象,如果是通過copy或者create或者retain,必須對應有一個releass
__bridge type: 通過該橋接方式,那么CoreFoundation對應的對象需要手動來釋放,Foundation框架的對象如果是在ARC環(huán)境下面,則不需手動釋放
__bridge_transfer type: 通過該橋接方式,那么CoreFoundation對應的對象表示已經(jīng)交給Foundation對象進行管理,如果是在ARC環(huán)境下面,不需要釋放任何一個對象

橋接有三種方式:

  • (__bridge type)(expression) : 只是讓NSFoundation框架暫時使用CF框架對象,注意需要手動釋放 Core Foundation 對象,用CFRelease( )函數(shù)。
  • (__bridge_transfer type)(expression)/ CFBridgingRelease(expression) `: CF框架移交對象的管理權(quán)給NSFoundation框架,不需要手動釋放對象
    前兩種是將CF對象轉(zhuǎn)NSFoundation,
  • (__bridge_retained )(expression) 最后一個是NSFoundation轉(zhuǎn) CF對象,不常用

下面大致介紹一下通訊錄操作中常用的類型:

  • ABAddressBookRef:代表通訊錄對象,通過該對象開發(fā)人員不用過多的關(guān)注通訊錄的存儲方式,可以直接以透明的方式去訪問、保存(在使用AddressBook.framework操作聯(lián)系人時,所有的增加、刪除、修改后都必須執(zhí)行保存操作,類似于Core Data)等。
  • ABRecordRef:代表一個通用的記錄對象,可以是一條聯(lián)系人信息,也可以是一個群組,可以通過-
  • ABRecordGetRecordType()函數(shù)獲得具體類型。如果作為聯(lián)系人(事實上也經(jīng)常使用它作為聯(lián)系人),那么這個記錄記錄了一個完整的聯(lián)系人信息(姓名、性別、電話、郵件等),每條記錄都有一個唯一的ID標示這條記錄(可以通過ABRecordGetRecordID()函數(shù)獲得)。
  • ABPersonRef:代表聯(lián)系人信息,很少直接使用,實際開發(fā)過程中通常會使用類型為“kABPersonType”的-
  • ABRecordRef 來表示聯(lián)系人(由此可見ABPersonRef其實是一種類型為“kABPersonType”的ABRecordRef)
  • ABGroupRef:代表群組,與ABPersonRef類似,很少直接使用ABGroupRef,而是使用類型為“kABGroupType”的ABRecordRef來表示群組,一個群組可以包含多個聯(lián)系人,一個聯(lián)系人也同樣可以多個群組。

首先看一下常用的操作通訊錄記錄的方法:

由于通訊錄操作的關(guān)鍵是對ABRecordRef的操作

  • ABPersonCreate():創(chuàng)建一個類型為“kABPersonType”的ABRecordRef。
  • ABRecordCopyValue():取得指定屬性的值。
  • ABRecordCopyCompositeName():取得聯(lián)系人(或群組)的復合信息(對于聯(lián)系人則包括:姓、名、公司等信息,對于群組則返回組名稱)。
  • ABRecordSetValue():設置ABRecordRef的屬性值。注意在設置ABRecordRef的值時又分為單值屬性和多值屬性:單值屬性設置只要通過ABRecordSetValue()方法指定屬性名和值即可;
  • 多值屬性則要先通過創(chuàng)建一個ABMutableMultiValueRef類型的變量,然后通過ABMultiValueAddValueAndLabel()方法依次添加屬性值,最后通過ABRecordSetValue()方法將ABMutableMultiValueRef類型的變量設置為記錄值。
  • ABRecordRemoveValue():刪除指定的屬性值
通訊錄的訪問步驟一般如下:
  1. 調(diào)用ABAddressBookCreateWithOptions()方法創(chuàng)建通訊錄對象ABAddressBookRef。
  2. 調(diào)用ABAddressBookRequestAccessWithCompletion()方法獲得用戶授權(quán)訪問通訊錄。
  3. 調(diào)用ABAddressBookCopyArrayOfAllPeople()、ABAddressBookCopyPeopleWithName()方法查詢聯(lián)系人信息。
  4. 讀取聯(lián)系人后如果要顯示聯(lián)系人信息則可以調(diào)用ABRecord相關(guān)方法讀取相應的數(shù)據(jù);如果要進行修改聯(lián)系人信息,則可以使用對應的方法修改ABRecord信息,然后調(diào)用ABAddressBookSave()方法提交修改;如果要刪除聯(lián)系人,則可以調(diào)用ABAddressBookRemoveRecord()方法刪除,然后調(diào)用ABAddressBookSave()提交修改操作。
  5. 也就是說如果要修改或者刪除都需要首先查詢對應的聯(lián)系人,然后修改或刪除后提交更改。如果用戶要增加一個聯(lián)系人則不用進行查詢,直接調(diào)用ABPersonCreate()方法創(chuàng)建一個ABRecord然后設置具體的屬性,調(diào)用ABAddressBookAddRecord方法添加即可。

獲取通訊錄狀態(tài)(ios9.0以前的)

     kABAuthorizationStatusNotDetermined = 0, 沒有決定是否授權(quán)
     kABAuthorizationStatusRestricted,  受限制     
     kABAuthorizationStatusDenied,  拒絕
     kABAuthorizationStatusAuthorized  授權(quán)

至于iOS9以后的通訊錄狀態(tài)kAB換成CN即可 ,比如拒絕狀態(tài)由kABAuthorizationStatusDenied->CNAuthorizationStatusDenied,其他的類比 即可
還有一個最大的變化,就是iOS9以后 訪問通訊錄屬性,要事先設置好,否則會奔潰
下面是列表

// 姓名前綴
CNContactNamePrefixKey     
// 名                 
CNContactGivenNameKey                       
// 中間名
CNContactMiddleNameKey  
// 姓                   
CNContactFamilyNameKey            
// 婚前姓         
CNContactPreviousFamilyNameKey
// 姓名后綴
CNContactNameSuffixKey   
// 昵稱                   
CNContactNicknameKey                        
// 公司
CNContactOrganizationNameKey                
// 部門
CNContactDepartmentNameKey                  
// 職位
CNContactJobTitleKey                        
// 名字拼音或音標
CNContactPhoneticGivenNameKey
// 中間名拼音或音標              
CNContactPhoneticMiddleNameKey
// 姓拼音或音標
CNContactPhoneticFamilyNameKey  
// 公司拼音或音標            
CNContactPhoneticOrganizationNameKey      
// 生日  
CNContactBirthdayKey   
// 農(nóng)歷                    
CNContactNonGregorianBirthdayKey    
// 備注        
CNContactNoteKey                            
// 圖片
CNContactImageDataKey                       
// 縮略圖
CNContactThumbnailImageDataKey              
// 圖片是否允許訪問
CNContactImageDataAvailableKey              
// 類型
CNContactTypeKey                            
// 號碼
CNContactPhoneNumbersKey                    
// 電子郵件
CNContactEmailAddressesKey                  
// 地址
CNContactPostalAddressesKey                 
// 日期
CNContactDatesKey   
// URL                        
CNContactUrlAddressesKey                    
// 關(guān)聯(lián)人
CNContactRelationsKey                       
// 社交
CNContactSocialProfilesKey                  
// 即時通訊
CNContactInstantMessageAddressesKey

需要什么,要事先聲明好

NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];

像我這個,就是聲明了。姓,名,手機號。

備注:
1.上文中所指的以Ref結(jié)尾的對象事實上是該對象的指針(或引用),在C語言的框架中多數(shù)類型會以Ref結(jié)尾,這個類型本身就是一個指針,定義時不需要加“*”
2.通常方法中包含copy、create、new、retain等關(guān)鍵字的方法創(chuàng)建的變量使用之后需要調(diào)用對應的release方法釋放。例如:使用ABPersonCreate();創(chuàng)建完ABRecordRef變量后使用CFRelease()方法釋放。
3.在與很多C語言框架交互時可以都存在Obj-C和C語言類型之間的轉(zhuǎn)化(特別是Obj-C和Core Foundation框架中的一些轉(zhuǎn)化),此時可能會用到橋接,只要在強轉(zhuǎn)之后前面加上”__bridge”即可,經(jīng)過橋接轉(zhuǎn)化后的類型不需要再去手動維護內(nèi)存,也就不需要使用對應的release方法釋放內(nèi)存。
Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,300評論 6 13
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,733評論 25 709
  • 《精要主義》(以下記錄為本書前言部分) 精要主義,追求的是“更少,但更好”。這意味著不是偶爾為之,而是把追求“更少...
    開拓者2021閱讀 296評論 0 0
  • 我們,一個多么歡快的詞語。 初中四年,少有人和我吵架。我不算大,卻也不小,但我的小脾氣確是出了名的火爆。我不懂得去...
    粽子啊閱讀 166評論 3 2
  • 我快樂只因你知我愛我自光陰荏苒此生便無蹉跎煩惱憂恐交相錯怕相聚甚少離別多艱險人世你走空留我故惜每日每夜形影成雙無論...
    夏曉蟬意閱讀 330評論 2 1

友情鏈接更多精彩內(nèi)容