通讯录访问

前言:

QQ通讯录、微信电话本会使用iOS的通讯录,iOS中的通讯录是存储在数据库中的,由于iOS的权限设计,开发人员是不允许直接访问通讯录数据库的。在iOS中带有一个Contacts应用程序来管理联系人,但是有些时候我们希望自己的应用能够访问或者修改这些信息,本节我们就来看一下如何访问通讯录中的数据。

iOS关于通讯录的开发有两种,一种是直接调用系统的通讯录界面,根据回调信息处理数据,另一种是直接获取系统的通讯录,完全自定义UI,并且可以通过官方给我们提供的接口进行读写。在iOS9前后,通讯录的访问方式有了重大更新,以前使用的框架都被弃用,本文主要对新的框架的使用做介绍,被弃用的框架会在后面的拓展部分做一下简单说明。

下面是对通讯录访问的几个框架的简单说明:
这里写图片描述


1. Contacts

iOS9中,苹果介绍了新的Contacts。允许用户使用Objective-C的API和设备的通讯录进行交互,同样适用于Swift语言。比起之前通过AddressBook来读取联系人信息来说,这是一个巨大的进步。因为AddressBook没有Objective-C的API,非常难用,用Swift写的时候更是痛苦。通常情况下,AddressBook不容易理解和掌握,对于新手来说更甚。这所有的一切都成为了历史,新的Contacts框架更加容易理解和使用,完全可以即刻获取通讯录,并可以进行创建和更新操作,与之相关的开发过程也可以被戏剧般地缩短,能够迅速地完成对通讯录的变更和修改。

通讯录的主要数据来源应该存在于设备中的数据库。但是,应用需要通讯录数据时,Contacts框架不会只从数据库中查找。事实上应用软件还会从其他地方查找,像iCloud账号(如果你创建并连接了该账号的话),向应用软件统一返回来自于不同数据源的通讯录数据。这项功能非常有用,因为你不用对设备以外的数据库做单独查询以获得通讯录信息,而是能够按自己的意愿管理,一劳永逸。 使用Contacts 从Framework中返回的联系人是统一的,这意味着,如果你有从不同的数据源来的相同联系人数据,他们会自动合并,无需手动进行合并的操作。

Contacts框架含有许多特定用途的类,所有这些类各司其职。下面我们来看一下Contacts框架中几个常用的类:

  • CNContactStore类,是用得最多的一个类,CNContactStore从代码层面代表通讯录数据库,提供不同方法执行相关操作。像获取数据,保存和更新记录,授权查询和授权请求等。
  • CNContact类代表通讯录中单独一条记录,但是CNContact一旦在内存中被实例化就是不可变的了。如果你想要创建一条新的,或者对现存的记录做更新的话,那就必须使用CNMutableContact类。
  • CNContactFetchRequest类,获取通讯录中通讯信息的请求对象,向应用软件导入通讯录的时候,很少会需要所有的信息。将Contacts框架查询到的所有信息悉数获取的做法会导致进程吞噬资源,除非真得需要使用所有数据,否则应该避免这样的操作。我们可以在初始化CNContactFetchRequest时设置请求的具体参数。例如,你可以获取First Name和Last Name,还可以获取通讯住址,以及电话号码。不动那些不需要的数据,节省了很多资源。

下面是使用Contacts框架的而基本步骤:

  • 集成Contacts框架并导入头文件
  • 创建通信录对象CNContactStore
  • 获取授权状态 并判断授权状态,如果不是已经授权,则直接返回
  • 创建获取通信录的请求对象CNContactFetchRequest
  • 通过请求遍历通信录 获取所有的联系人
  • 获取联系人信息
    这里写图片描述

1.1 获取系统通讯录联系人列表

示例代码:

#import "ViewController.h"
#import <Contacts/Contacts.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

// 获取所有的联系人信息
- (IBAction)getContact:(id)sender{


    // 1.创建通信录对象CNContactStore,CNContactStore是一个用来读取和保存联系人的新的类。可以用来进行展示和保存联系人群组操作
    CNContactStore *contactStore = [[CNContactStore alloc] init];

    // 2.获取授权状态并判断授权状态,如果不是已经授权,则直接返回
    [contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
        // 完成授权后就不会再次请求,避免了重复的UI操作

        CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
        if (status != CNAuthorizationStatusAuthorized) return;

        // 3.创建获取通信录的请求对象。当我们有了这个联系人数据库的引用后,我们需要创建一个指定条件的请求,通过这个请求去获取某些结果。创建一个CNContactFetchRequest,我们可以通过设置contactkeys的数组,来获取我们需要的结果。
        // 3.1 拿到所有打算获取的属性对应的key
        NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
        // 3.2 创建CNContactFetchRequest对象
        CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:keys];

        // 4.通过请求遍历通信录 获取所有的联系人,从CNContactStore 中遍历所有符合我们需求的联系人。这个request 没有加任何的条件,所以会返回全部的联系人,包含我们需要的 keys。我们把每一条记录都逐个保存到一个数组中,返回。
        [contactStore enumerateContactsWithFetchRequest:request error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {

            // 5.获取联系人信息
            // 获取联系人的姓名
            NSString *lastname = contact.familyName;
            NSString *firstname = contact.givenName;
            NSLog(@"%@ %@", lastname, firstname);

            // 获取联系人的电话号码
            NSArray *phoneNums = contact.phoneNumbers;
            for (CNLabeledValue *labeledValue in phoneNums) {
                // 2.1.获取电话号码的KEY
                NSString *phoneLabel = labeledValue.label;

                // 2.2.获取电话号码
                CNPhoneNumber *phoneNumer = labeledValue.value;
                NSString *phoneValue = phoneNumer.stringValue;

                NSLog(@"%@ %@", phoneLabel, phoneValue);
            }
        }];
    }];
}
@end

注意:使用Contacts框架时,特别是在获取通讯录信息的时候,应该在后台线程中执行这些操作。如果获取操作在主线程执行,耗费过多时间的话,那么应用软件会失去响应,最终导致不良的用户体验。

1.2 获取指定的联系人

除了上面我们获取通讯录中所有的联系人之外,我们也可以通过指定谓词的方式来获取想要的联系人信息,Predicate:对于返回的结果来说,NSPredicate对象实际上起到了过滤的作用。这里要强调的重点是,只有CNContact类的谓词(CNContact Predicates)才可以,自己创建的普通谓词不行。在CNContact类所支持的诸多谓词函数里,我们要用到一个叫做predicateForContactsMatchingName的函数。实现通过谓词检索联系人的关键方法就是unifiedContactsMatchingPredicate: keysToFetch:error:方法。下面,我们来指定联系人姓名获取对应的联系人信息。

示例代码:

#import "ViewController.h"
#import <Contacts/Contacts.h>

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

// 通过谓词获取部分通讯录信息
- (IBAction)getContactWithPredicate:(id)sender {


    // 0.生成谓词作为请求参数
    NSPredicate *predicate = [CNContact predicateForContactsMatchingName:@“"];


    // 1.创建通信录对象CNContactStore
    CNContactStore *contactStore = [[CNContactStore alloc] init];

    // 2.获取授权状态并判断授权状态
    [contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {

        CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];

        if (status != CNAuthorizationStatusAuthorized) {            granted = NO;// 停止遍历
        }

        // 3. 拿到所有打算获取的属性对应的key
        NSArray *keys = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];


        // 4. 通过谓词遍历通信录获取目标消息
        NSArray *contacts = [contactStore unifiedContactsMatchingPredicate:predicate keysToFetch:keys error:nil];

        for (CNContact *contact in contacts) {

            // 5.获取联系人信息
            // 获取联系人的姓名
            NSString *lastname = contact.familyName;
            NSString *firstname = contact.givenName;
            NSLog(@"%@ %@", lastname, firstname);

            // 获取联系人的电话号码
            NSArray *phoneNums = contact.phoneNumbers;
            for (CNLabeledValue *labeledValue in phoneNums) {
                // 2.1.获取电话号码的KEY
                NSString *phoneLabel = labeledValue.label;

                // 2.2.获取电话号码
                CNPhoneNumber *phoneNumer = labeledValue.value;
                NSString *phoneValue = phoneNumer.stringValue;

                NSLog(@"%@ %@", phoneLabel, phoneValue);
            }
        }
    }];
}
@end

1.3 修改联系人信息

Contacts框架还提供了CNMutableContact。这个类与CNContact相似,但这个类允许对某条记录的属性赋新值,也可以对现存的联系人记录进行创建和更新操作。实际的保存(和更新)过程是通过众所周知的CNContactStore类处理的,但那是创建新记录时的最后一步。当然,CNContactStore只是联系人数据的上下文,联系人的增、删、改等处理都是通过CNSaveRequest类来实现的。
CNSaveRequest是用于存储联系人的请求类,通过这个类,我们可以创建批量添加、修改或者删除联系人的请求。下面是CNSaveRequest的一些常用方法的说明:

属性 说明
-(void)addContact:(CNMutableContact )contact toContainerWithIdentifier:(nullable NSString )identifier; 添加一个联系人
-(void)updateContact:(CNMutableContact *)contact; 更新一个联系人
-(void)deleteContact:(CNMutableContact *)contact; 删除一个联系人
-(void)addGroup:(CNMutableGroup )group toContainerWithIdentifier:(nullable NSString )identifier; 添加一组联系人
-(void)updateGroup:(CNMutableGroup *)group; 更新一组联系人
-(void)deleteGroup:(CNMutableGroup *)group; 删除一组联系人
-(void)addSubgroup:(CNGroup )subgroup toGroup:(CNGroup )group NS_AVAILABLE(10_11, NA); 向组中添加子组
-(void)removeSubgroup:(CNGroup )subgroup fromGroup:(CNGroup )group NS_AVAILABLE(10_11, NA); 在组中删除子组
-(void)addMember:(CNContact )contact toGroup:(CNGroup )group; 向组中添加成员
-(void)removeMember:(CNContact )contact fromGroup:(CNGroup )group; 向组中移除成员

一般来说,使用CNMutableContact类给联系人的属性赋值与获取属性的操作正好相反。这也就是说比直接给简单的属性(First Name)单独赋值更复杂一些。下面是保存一个新的联系人的具体步骤:

  • 初始化一个新的联系人CNMutableContact
  • 设置联系人属性,有诸多的新的数据对象,比如电话,需要格外注意
  • 初始化一个联系人请求并制定联系人数据操作
  • 创建CNContactStore并写入请求

示例代码:

#import "ViewController.h"
#import <Contacts/Contacts.h>

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

// 创建一条新的联系人记录,并将这条记录存储到数据库里面。
- (IBAction)creatNewContacr:(id)sender {

    // 1. 初始化一个新的联系人
    CNMutableContact *newContact = [[CNMutableContact alloc] init];

    // 2. 设置联系人属性
    newContact.familyName = @"老王";
    newContact.givenName = @"去你家了";

    // 设置联系人家庭电话
    CNLabeledValue *homeValue = [CNLabeledValue labeledValueWithLabel:CNLabelHome value:[CNPhoneNumber phoneNumberWithStringValue:@"12345678"]];
    newContact.phoneNumbers = @[homeValue];
    // phoneNumbers属性是一个数组,数组中是才CNLabeledValue对象,CNLabeledValue对象主要用于创建一些联系人属性的键值对应,通过这些对应,系统会帮我们进行数据的格式化,例如CNLabelHome,就会将号码格式成家庭邮箱的格式,
    /*联系人电话的配置方式
     CONTACTS_EXTERN NSString * const CNLabelPhoneNumberiPhone
     CONTACTS_EXTERN NSString * const CNLabelPhoneNumberMobile
     CONTACTS_EXTERN NSString * const CNLabelPhoneNumberMain
     CONTACTS_EXTERN NSString * const CNLabelPhoneNumberHomeFax
     CONTACTS_EXTERN NSString * const CNLabelPhoneNumberWorkFax
     CONTACTS_EXTERN NSString * const CNLabelPhoneNumberOtherFax
     CONTACTS_EXTERN NSString * const CNLabelPhoneNumberPager
     */

    // 设置联系人邮箱
    CNLabeledValue *homeEmail = [CNLabeledValue labeledValueWithLabel:CNLabelHome value:@"[email protected]"];
    CNLabeledValue *workEmail =[CNLabeledValue labeledValueWithLabel:CNLabelWork value:@"[email protected]"];
    newContact.emailAddresses = @[homeEmail,workEmail];
    /* 联系人邮箱的配置方式
     CONTACTS_EXTERN NSString * const CNLabelHome//家庭
     CONTACTS_EXTERN NSString * const CNLabelWork//工作
     CONTACTS_EXTERN NSString * const CNLabelOther//其他
     CONTACTS_EXTERN NSString * const CNLabelEmailiCloud//邮箱
     CONTACTS_EXTERN NSString * const CNLabelURLAddressHomePage//url地址
     CONTACTS_EXTERN NSString * const CNLabelDateAnniversary// 日期
     */

    // 联系人地址
    CNMutablePostalAddress * homeAdress = [[CNMutablePostalAddress alloc]init];
    homeAdress.street = @"贝克街";
    homeAdress.city = @"伦敦";
    homeAdress.state = @"英国";
    homeAdress.postalCode = @"221B";
    newContact.postalAddresses = @[[CNLabeledValue labeledValueWithLabel:CNLabelHome value:homeAdress]];

    // 联系人生日:
    NSDateComponents * birthday = [[NSDateComponents  alloc]init];
    birthday.day=7;
    birthday.month=5;
    birthday.year=1992;
    newContact.birthday=birthday;

    // 3.初始化一个联系人请求并制定联系人数据操作
    CNSaveRequest *request = [[CNSaveRequest alloc] init];
    // 添加联系人
    [request addContact:newContact toContainerWithIdentifier:nil];

    // 4. 创建CNContactStore并写入请求
    // CNContactStore是一个用于存取联系人的上下文桥梁,把我们创建的添加联系人的请求写入:
    CNContactStore *contactStore = [[CNContactStore alloc] init];
    NSError *error = nil;
    // 写入请求
    BOOL isSuc = [contactStore executeSaveRequest:request error:&error];
    NSLog(@"%d",isSuc);

#pragma mark - 格式化联系人信息
    // iOS9中,ContactFramework也为开发者提供了非常方便的格式化信息的方法,还拿我们上面创建的联系人对象举例:
    // 1.获取格式化的联系人姓名
    NSString *foematter1 =[CNContactFormatter stringFromContact:newContact style:CNContactFormatterStyleFullName];
    /*CNContactFormatterStyle
     CNContactFormatterStyleFullName,//获取全名
     CNContactFormatterStylePhoneticFullName,//获取拼音全名
     */
    NSLog(@"%@",foematter1);
    // 2、获取格式化的联系人地址
    NSString *foematter2 =[CNPostalAddressFormatter stringFromPostalAddress:homeAdress style:CNPostalAddressFormatterStyleMailingAddress];
    NSLog(@"%@",foematter2);
}
@end

注意:给联系人填充数据时需要注意设置的参数必须为对应的参数类型:

  • 设置出生日期时,必须创建NSDateComponents对象并赋相应的属性值。
  • 设置头像时,必须要给其赋NSData类型的对象。
  • 设置邮箱地址时,必须给每个邮箱地址单独创建一个CNLabeledValue对象,然后将所有这些对象作为数组赋值给emailAddresses属性。

2. ContactsUI

开发过程中,如果项目对联系人界面定制要求不高的话, iOS9中,苹果介绍了新的ContactsUI方便我们直接访问通讯录。ContactUI框架除了提供代码层面访问通讯录的方式,还支持一些应用软件中默认的UI方式,可以直接用可视化的方式访问通讯录。所提供的UI与系统内置的通讯录非常相似,这就是说会有一个用来选取内容的视图控制器(CNContactPickerViewController)与详细信息页一起,这个控制器可以选取联系人和其相关的属性(这些内容在一定程度上可以定制化)。而详细页的视图控制器(CNContactViewController)用来显示联系人的详细信息,还用来做一些特定的操作(例如打电话)。
我们先来看一下如何使用下面是使用CNContactPickerViewController访问系统通讯录,下面是使用的具体步骤:

  • 导入ContactsUI.framwork 框架及头文件
  • 创建选择联系人的控制器,并设置代理
  • 弹出联系人控制器
  • 实现代理方法,注意:除了cancel之外,实现不同的代理方法后实现的效果也是不一样的
    这里写图片描述

2.1 在应用内部查看联系人列表

示例代码:

#import "ViewController.h"
#import <ContactsUI/ContactsUI.h>

@interface ViewController ()<CNContactViewControllerDelegate,CNContactPickerDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (IBAction)enterContectPickerViewController:(id)sender {
    // 1.创建选择联系人的控制器
    CNContactPickerViewController *contactPVC = [[CNContactPickerViewController alloc] init];

    // 设置代理
    contactPVC.delegate = self;

    // 联系人CNContact属性键显示卡片的联系细节,如果不设置显示所有属性。
    contactPVC.displayedPropertyKeys = @[CNContactFamilyNameKey,CNContactGivenNameKey,CNContactPhoneNumbersKey];//设置后部分属性无法获得

    // 通过谓词限制通讯录信息的使用
    contactPVC.predicateForEnablingContact = [NSPredicate predicateWithFormat:@"birthday != nil"];
    /*
     predicateForEnablingContact: 这个可能是你要用的最多的一个predicate。有了它,你可以指定在picker controller里面哪些联系人信息可用。你可以用那个方法来过滤出需要的联系人,比如仅仅让那些有着有效地生日日期的联系人才可以被选择。
     predicateForSelectionOfContact: 有了它,你可以控制picker view controller在哪种条件下可以返回被选中的联系人,以及对于其它的选中模式,什么时候它可以展示details view controller .
     predicateForSelectionOfProperty: 使用它,你可以指定一个属性的默认响应是否应该被执行(比如当点击一个电话号码时是否需要创建打电话行为),或者被点击的属性是否应该被返回。
     */

    // 2.弹出控制器
    [self presentViewController:contactPVC animated:YES completion:nil];
}

// 注意代理方法除了cancel之外,实现不同的代理方法后实现的效果也是不一样的
#pragma mark - CNContactPickerDelegate
// 当选中某一个联系人时会执行该方法
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact {

    // 1.获取联系人的姓名
    NSString *lastname = contact.familyName;
    NSString *firstname = contact.givenName;
    NSLog(@"联系人姓名:%@ %@", lastname, firstname);

    // 2.获取联系人的电话号码
    for (CNLabeledValue *labeledValue in contact.phoneNumbers) {
        // 2.1.获取电话号码的KEY
        NSString *phoneLabel = labeledValue.label;
        // 2.2.获取电话号码
        CNPhoneNumber *phoneNumer = labeledValue.value;
        NSString *phoneValue = phoneNumer.stringValue;

        NSLog(@"联系人电话%@ %@", phoneLabel, phoneValue);
    }

}

// 当选中某一个联系人的某一个属性时会执行该方法,只实现该代理方法可以看到通讯录信息的详情页
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty {

    NSLog(@"选中的联系人:%@",contactProperty.contact);
}

// 点击了取消按钮会执行该方法
- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker {

}

// 实现下面的代理方法后,可以实现联系人的多选
// 选中多个联系人时调用该方法
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContacts:(NSArray<CNContact*> *)contacts {

    for (CNContact *contact in contacts) {

        // 获取一个联系人
        // 1.获取联系人的姓名
        NSString *lastname = contact.familyName;
        NSString *firstname = contact.givenName;
        NSLog(@"联系人姓名:%@ %@", lastname, firstname);

        // 2.获取联系人的电话号码
        for (CNLabeledValue *labeledValue in contact.phoneNumbers) {
            // 2.1.获取电话号码的KEY
            NSString *phoneLabel = labeledValue.label;
            // 2.2.获取电话号码
            CNPhoneNumber *phoneNumer = labeledValue.value;
            NSString *phoneValue = phoneNumer.stringValue;

            NSLog(@"联系人电话%@ %@", phoneLabel, phoneValue);
        }

        // 保存选中的联系人
        [_contactArray addObject:contact];
    };
}

// 当选中多个联系人的多个属性时会执行该方法,注释上面的方法后该方法会被调用
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperties:(NSArray<CNContactProperty*> *)contactProperties {
}
@end

注意:上面的几个代理方法不要同时复写,因为复写不同的代理方法,我们看到的通讯录控制器界面效果也是不一样的。

-(void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact // 选中单个联系然后返回

-(void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty // 选中联系人后进人联系人的信息详情界面,选中某条信息后返回

-(void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperties:(NSArray<CNContactProperty*> *)contactProperties // 效果同上,但是返回的参数为空

-(void)contactPicker:(CNContactPickerViewController *)picker didSelectContacts:(NSArray<CNContact*> *)contacts // 可以选中多个联系人信息并返回

2.2 在应用内部查看联系人信息

如果要求在一个新的试图控制器里面展示一个被选中的联系人的详细信息。我们可以使用ContactsUI 框架提供的CNContactViewController类来实现。 使用它我们不仅可以查看联系人的数据,还可以编辑数据。

注意: 在我们展示一个 CNContactViewController对象之前,我们必须确保这个选中的联系人的细节信息对应的所有关键字都是可用的。 CNContactViewController类提供了一个方法叫做 areKeysAvailable:,我们可以利用它来保证CNContactViewController需要的所有的关键字都存在。这个方法包含了仅仅一个变量,一个关键字字符串或者字符串描述器(key descriptors 同我们多次用来获取联系人的关键字字符串类似)。在CNContactViewController中,我们必须给CNContactViewController.descriptorForRequiredKeys 设置一个特定关键字数值组成的数组参数,这个类方法将会自动检查所有的关键字是否存在。如果这些关键字都存在,我们就展示CNContactViewController。

下面是CNContactViewController使用的步骤:

  • 初始化CNContactViewController,并传入要显示的联系人信息
  • 设置代理并确定想展示的内容,当然在此之前需要判断传入的联系人信息是否包含指定的内容
  • 添加一个导航栏,系统默认现实操作按钮,可以将返回的方法写在代理方法中
  • 进入CNContactViewController界面

示例代码:

#import "ViewController.h"
#import <ContactsUI/ContactsUI.h>

@interface ViewController ()<CNContactViewControllerDelegate,CNContactPickerDelegate>
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

// 1. 进入通讯录实现联系人的点选
- (IBAction)changeContact:(id)sender {

    // 创建选择联系人的控制器
    CNContactPickerViewController *contactPVC = [[CNContactPickerViewController alloc] init];
    // 设置代理
    contactPVC.delegate = self;
    // 弹出控制器
    [self presentViewController:contactPVC animated:YES completion:nil];

}

#pragma mark - CNContactPickerDelegate
// 2.实现点选的代理,其他代理方法根据自己需求实现
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact{

    // 返回主界面时进入CNContactViewController现实联系人详情
    [picker dismissViewControllerAnimated:YES completion:^{

        // 3.获取选中的联系人 copy一份可写的Contact对象,不要尝试alloc一类
        CNMutableContact *selectedContact = [contact mutableCopy];


        // 4.设置可显示的联系人信息,并判断选中的联系人是否有对应信息
        NSArray *keys = @[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey];
        if ([selectedContact areKeysAvailable:keys]) {

            // 5.跳转到新建联系人页面
            // 初始化CNContactViewController,并传入要显示的联系人信息
            CNContactViewController *contactVC = [CNContactViewController viewControllerForNewContact:selectedContact];
            // 设置代理
            contactVC.delegate = self;
            // 确定想展示的内容
            contactVC.displayedPropertyKeys = keys;

            // 添加一个导航栏,系统默认现实操作按钮
            UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:contactVC];
            [self presentViewController:navigation animated:YES completion:nil];
        }
    }];
}

#pragma mark - CNContactViewControllerDelegate
// 将要展示联系人信息时调用该方法
- (BOOL)contactViewController:(CNContactViewController *)viewController shouldPerformDefaultActionForContactProperty:(CNContactProperty *)property {

    return YES;// 是否展示默认的方法,显示联系人属性
}

// 结束联系人的编辑时调用该方法
- (void)contactViewController:(CNContactViewController *)viewController didCompleteWithContact:(nullable CNContact *)contact {
    // 回到主界面
    [viewController dismissViewControllerAnimated:YES completion:nil];
}

@end

注释:
在上面的代码中我们可以看到,进入联系人界面后,我们不仅可以实现打电话等功能,而且可以对联系人信息进行修改。事实上,我们也可以使用代码来对联系人信息做修改或是保存一个新的联系人到通讯录。具体步骤如下:

  • 创建联系人对象,必须是可变的
  • 为联系人对象赋值
  • 创建新建好友页面,显示新的联系人数据
  • 跳转到联系人详情界面

示例代码:

#import "ViewController.h"
#import <ContactsUI/ContactsUI.h>

@interface ViewController ()<CNContactViewControllerDelegate,CNContactPickerDelegate>
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

#pragma mark - 保存新联系人实现
- (IBAction)saveNewContact:(id)sender {

    // 1.创建联系人对象对象,必须是可变的
    CNMutableContact *contact = [[CNMutableContact alloc] init];

    // 2.为contact赋值
    [self setValueForContact:contact existContect:NO];

    // 3.创建新建好友页面
    CNContactViewController *controller = [CNContactViewController viewControllerForNewContact:contact];
    // 代理内容根据自己需要实现
    controller.delegate = self;
    // 4.跳转
    UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:controller];
    [self presentViewController:navigation animated:YES completion:nil];

}

// 设置要保存的contact对象
- (void)setValueForContact:(CNMutableContact *)contact existContect:(BOOL)exist {

    if (!exist) {// 判断是否为旧的联系人
        // 联系人名字
        contact.nickname = @"oriccheng";
        contact.familyName = @"老王";
        contact.givenName = @"小王";
        // 头像
        UIImage *logo = [UIImage imageNamed:@""];
        NSData *dataRef = UIImagePNGRepresentation(logo);
        contact.imageData = dataRef;
    }

    // 联系人电话
    CNLabeledValue *phoneNumber = [CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberMobile value:[CNPhoneNumber phoneNumberWithStringValue:@"18888888888"]];

    if ([contact.phoneNumbers count] >0) {
        // 现有联系人已经存有电话的情况

        NSMutableArray *phoneNumbers = [[NSMutableArray alloc] initWithArray:contact.phoneNumbers];
        [phoneNumbers addObject:phoneNumber];
        contact.phoneNumbers = phoneNumbers;

    }else{
        contact.phoneNumbers = @[phoneNumber];
    }

    // 网址:CNLabeledValue *url = [CNLabeledValue labeledValueWithLabel:@"" value:@""];
    // 邮箱:CNLabeledValue *mail = [CNLabeledValue labeledValueWithLabel:CNLabelWork value:self.poiData4Save.mail];

    // 地址,PostalAddress对应的才是地址
    CNMutablePostalAddress *address = [[CNMutablePostalAddress alloc] init];
    address.state = @"辽宁省";
    address.city = @"沈阳市";
    address.postalCode = @"111111";
    // 外国人好像都不强调区的概念,所以和具体地址拼到一起
    address.street = @"沈河区惠工街10号";
    //生成的上面地址的CNLabeledValue,其中可以设置类型CNLabelWork等等
    CNLabeledValue *addressLabel = [CNLabeledValue labeledValueWithLabel:CNLabelWork value:address];

    if ([contact.postalAddresses count] >0) {
        NSMutableArray *addresses = [[NSMutableArray alloc] initWithArray:contact.postalAddresses];
        [addresses addObject:addressLabel];
        contact.postalAddresses = addresses;
    }else{
        contact.postalAddresses = @[addressLabel];
    }

}

#pragma mark - CNContactViewControllerDelegate
// 将要展示联系人信息时调用该方法
- (BOOL)contactViewController:(CNContactViewController *)viewController shouldPerformDefaultActionForContactProperty:(CNContactProperty *)property {

    return YES;// 是否展示默认的方法,显示联系人属性
}

// 结束联系人的编辑时调用该方法
- (void)contactViewController:(CNContactViewController *)viewController didCompleteWithContact:(nullable CNContact *)contact {
    // 回到主界面
    [viewController dismissViewControllerAnimated:YES completion:nil];
}
@end

注释:
如果想要对以前的联系人信息做修改时,同样可以使用上面的- (void)setValueForContact:(CNMutableContact *)contact existContect:(BOOL)exist方法,exist参数设置为NO,当然,在调用之前你需要先选中一个联系人。

猜你喜欢

转载自blog.csdn.net/qq_32510689/article/details/51721972