• iOS学习笔记29-系统服务(二)通讯录


    一、通讯录

    iOS中的通讯录是存储在数据库中的,由于iOS的权限设计,开发人员是不允许直接访问通讯录数据库的,实现通讯录操作需要使用到AddressBook.framework框架。

    AddressBook.framework框架:
    • 可以从底层去操作通讯录的所有信息,做到精确控制
    • 是基于C语言编写的,无法使用ARC管理内存,需要开发者手动管理内存
    • 需要自构UI界面

    iOS还提供了另外一个框架来供开发者操作通讯录,那就是AddressBookUI.framework

    AddressBookUI.framework框架:
    • 该框架封装AddressBook.framework,向外提供现成视图控制器使用
    • 可以使用ARC管理内存
    • 高度封装化,界面固定,可定制性差

    这两个框架各有各的优点,各有各的缺点,具体采用哪一种去操作通讯录看具体需求决定。

    二、AddressBook

    AddressBook.framework框架是基于C语言的,缺少面向对象的思想,所以我们可以把里面一些结构体理解为一个“类”

    首先我们来了解几个核心结构体:
    1. ABAddressBookRef
      通讯录对象,全局管理通讯录操作,比如修改保存等
    2. ABRecordRef
      通用的记录对象,可以是一条联系人信息,也可以是一个群组,通过具体类型进行区分,每条记录都有一个唯一ID标识
    3. ABPersonRef
      联系人信息,不常用,可以用类型为kABPersonTypeABRecordRef代替。
    4. ABGroupRef
      群组信息,不常用,可以用类型为kABGroupTypeABRecordRef代替。
    常使用到关于记录Record的C语言函数:
    /* 获取一条记录对象的唯一标识ID */
    ABRecordID ABRecordGetRecordID(ABRecordRef record);
    /* 创建一条记录对象,类型为kABPersonType,表示一条联系人信息 */
    ABRecordRef ABPersonCreate(void);
    /* 创建一条记录对象,类型为kABGroupType,表示一条群组信息 */
    ABRecordRef ABGroupCreate(void);
    /* 获取指定属性的值 */
    CFTypeRef ABRecordCopyValue(ABRecordRef record, ABPropertyID property);
    /* 设置纪录的属性值,返回设置是否成功 */
    bool ABRecordSetValue(
        ABRecordRef record, /* 记录 */
        ABPropertyID property, /* 属性 */
        CFTypeRef value, /* 值,可以是单值,也可以是多重值 */
        CFErrorRef* error /* 错误信息 */
    );
    /* 向多重值添加单值 */
    bool ABMultiValueAddValueAndLabel(
        ABMutableMultiValueRef multiValue, /* 多重值 */
        CFTypeRef value, /* 单值 */
        CFStringRef label, /* 单值对应的属性名 */
        ABMultiValueIdentifier *outIdentifier /* 多重值的标示 */
    );
    /* 从多重值中取出指定索引的单值 */
    CFTypeRef ABMultiValueCopyValueAtIndex(
        ABMultiValueRef multiValue, 
        CFIndex index
    );
    /* 删除指定的属性值 */
    bool ABRecordRemoveValue(
        ABRecordRef record, /* 记录 */
        ABPropertyID property, /* 属性 */
        CFErrorRef* error /* 错误信息 */
    );
    
    常使用到的通讯录操作的函数
    /* 创建通讯录对象 */
    ABAddressBookRef ABAddressBookCreate(void);
    /* 操作通讯录用户授权,注意无论成功与否回调都会调用 */
    void ABAddressBookRequestAccessWithCompletion(
        ABAddressBookRef addressBook,  /* 通讯录 */
        ABAddressBookRequestAccessCompletionHandler completion /* 回调 */
    );
    /* 获取通讯录所有的记录 */
    CFArrayRef ABAddressBookCopyArrayOfAllPeople(ABAddressBookRef addressBook);
    /* 添加记录进通讯录 */
    bool ABAddressBookAddRecord(
        ABAddressBookRef addressBook, /* 通讯录 */
        ABRecordRef record, /* 记录 */
        CFErrorRef* error /* 错误信息 */
    );
    /* 从通讯录删除记录 */
    bool ABAddressBookRemoveRecord(
        ABAddressBookRef addressBook,/* 通讯录 */
        ABRecordRef record, /* 记录 */
        CFErrorRef* error/* 错误信息 */
    );
    /* 从通讯录中获取一个记录,根据记录ID */
    ABRecordRef ABAddressBookGetPersonWithRecordID(
        ABAddressBookRef addressBook, /* 通讯录 */
        ABRecordID recordID /* 记录ID */
    );
    /* 保持通讯录,修改了通讯录需要保存提交修改 */
    bool ABAddressBookSave(
        ABAddressBookRef addressBook, /* 通讯录 */
        CFErrorRef* error /* 错误信息 */
    );
    
    通讯录使用步骤:
    1. 创建通讯录对象ABAddressBookRef
    2. 请求用户授权操作通讯录ABAddressBookRequestAccessWithCompletion
    3. 查询所有通讯录的记录ABAddressBookCopyArrayOfAllPeople
    4. 添加记录,删除记录,修改记录
    5. 修改通讯录后,记住要通讯录保存ABAddressBookSave
    下面是实际代码:
    1. 创建通讯录并请求授权
    /* 请求访问通讯录并获取通讯录所有记录 */
    - (void)requestAddressBook{
        //创建通讯录对象
        self.addressBook = ABAddressBookCreate();
        
        //请求访问用户通讯录,注意无论成功与否block都会调用
        ABAddressBookRequestAccessWithCompletion(self.addressBook, ^(bool granted, CFErrorRef error) {
            if (!granted) {
                NSLog(@"未获得通讯录访问权限!");
            }
            //获取所有通讯录记录
            [self initAllPerson];
            //刷新表格
            [self.tableView reloadData];
        });
    }
    
    /* 取得所有通讯录记录 */
    - (void)initAllPerson{
        //取得通讯录访问授权
        ABAuthorizationStatus authorization = ABAddressBookGetAuthorizationStatus();
        //如果未获得授权
        if (authorization != kABAuthorizationStatusAuthorized) {
            NSLog(@"尚未获得通讯录访问授权!");
            return ;
        }
        //取得通讯录中所有人员记录
        CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(self.addressBook);
        self.allPerson = (__bridge NSMutableArray *)allPeople;
        //释放资源
        CFRelease(allPeople);
    }
    
    2. 添加联系人
    
    /**
     *  添加一条记录
     *
     *  @param firstName  名
     *  @param lastName   姓
     *  @param iPhoneName iPhone手机号
     */
    - (void)addPersonWithFirstName:(NSString *)firstName
                          lastName:(NSString *)lastName
                        workNumber:(NSString *)workNumber
    {
        //创建一条记录
        ABRecordRef recordRef = ABPersonCreate();
        //添加名
        ABRecordSetValue(recordRef,kABPersonFirstNameProperty,(__bridge CFTypeRef)(firstName),NULL);
        //添加姓
        ABRecordSetValue(recordRef,kABPersonLastNameProperty,(__bridge CFTypeRef)(lastName),NULL);
        //创建一个多值属性,因为手机号可以有多个
        ABMutableMultiValueRef multiValueRef = ABMultiValueCreateMutable(kABStringPropertyType);
        //向多值属性中添加工作电话
        ABMultiValueAddValueAndLabel(multiValueRef,(__bridge CFStringRef)(workNumber),kABWorkLabel,NULL);
        //添加属性到指定记录,这里添加的是多值属性
        ABRecordSetValue(recordRef, kABPersonPhoneProperty, multiValueRef, NULL);
        //添加记录到通讯录
        ABAddressBookAddRecord(self.addressBook, recordRef, NULL);
        //保存通讯录,提交更改
        ABAddressBookSave(self.addressBook, NULL);
        //释放资源
        CFRelease(recordRef);
        CFRelease(multiValueRef);
    }
    
    3. 删除联系人
    /* 删除指定的记录 */
    - (void)removePersonWithRecord:(ABRecordRef)recordRef{
        ABAddressBookRemoveRecord(self.addressBook, recordRef, NULL);//删除
        ABAddressBookSave(self.addressBook, NULL);//删除之后提交更改
    }
    /* 根据姓名删除记录 */
    - (void)removePersonWithName:(NSString *)personName{
        CFStringRef personNameRef = (__bridge CFStringRef)(personName);
        //根据人员姓名查找
        CFArrayRef recordsRef = ABAddressBookCopyPeopleWithName(self.addressBook, personNameRef);
        CFIndex count = CFArrayGetCount(recordsRef);//取得记录数
        for (CFIndex i=0; i<count; ++i) {
            ABRecordRef recordRef = CFArrayGetValueAtIndex(recordsRef, i);//取得指定的记录
            ABAddressBookRemoveRecord(self.addressBook, recordRef, NULL);//删除
        }
        //删除之后提交更改
        ABAddressBookSave(self.addressBook, NULL);
        CFRelease(recordsRef);
    }
    
    4. 修改联系人
    /**
     *  根据记录ID修改联系人信息
     *
     *  @param recordID   记录唯一ID
     *  @param firstName  姓
     *  @param lastName   名
     *  @param homeNumber 工作电话
     */
    - (void)modifyPersonWithRecordID:(ABRecordID)recordID
                           firstName:(NSString *)firstName
                            lastName:(NSString *)lastName
                          workNumber:(NSString *)workNumber
    {
        //根据记录ID获取一条记录
        ABRecordRef recordRef = ABAddressBookGetPersonWithRecordID(self.addressBook, recordID);
        //添加名
        ABRecordSetValue(recordRef,kABPersonFirstNameProperty,(__bridge CFTypeRef)(firstName),NULL);
        //添加姓
        ABRecordSetValue(recordRef,kABPersonLastNameProperty,(__bridge CFTypeRef)(lastName),NULL);
        //创建一个多值属性,因为手机号可以有多个
        ABMutableMultiValueRef multiValueRef = ABMultiValueCreateMutable(kABStringPropertyType);
        //向多值属性中添加工作电话
        ABMultiValueAddValueAndLabel(multiValueRef,(__bridge CFStringRef)(workNumber),kABWorkLabel,NULL);
        //添加属性到指定记录,这里添加的是多值属性
        ABRecordSetValue(recordRef, kABPersonPhoneProperty, multiValueRef, NULL);
        //保存记录,提交更改
        ABAddressBookSave(self.addressBook, NULL);
        //释放资源
        CFRelease(multiValueRef);
    }
    
    5. UITableView显示
    #pragma mark - TableView代理和数据源
    - (NSInteger)tableView:(UITableView *)tableView
     numberOfRowsInSection:(NSInteger)section
    {
        if (!self.allPerson) {
            return 0;
        }
        return self.allPerson.count;
    }
    - (UITableViewCell *)tableView:(UITableView *)tableView
             cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *key = @"cellIdentify";
        UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:key];
        if (!cell) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
                                          reuseIdentifier:key];
        }
        //取得一条人员记录
        ABRecordRef recordRef = (__bridge ABRecordRef)self.allPerson[indexPath.row];
        //取得记录中得信息,注意这里进行了强转,不用自己释放资源
        NSString *firstName = (__bridge NSString *)ABRecordCopyValue(recordRef, kABPersonFirstNameProperty);
        NSString *lastName = (__bridge NSString *)ABRecordCopyValue(recordRef, kABPersonLastNameProperty);
        //获取手机号,注意手机号是ABMultiValueRef类,有可能有多条
        ABMultiValueRef phoneNumbersRef = ABRecordCopyValue(recordRef, kABPersonPhoneProperty);
        long count = ABMultiValueGetCount(phoneNumbersRef);
        for(int i = 0;i < count;++i){
            NSString *phoneLabel = (__bridge NSString *)(ABMultiValueCopyLabelAtIndex(phoneNumbersRef, i));
            NSString *phoneNumber = (__bridge NSString *)(ABMultiValueCopyValueAtIndex(phoneNumbersRef, i));
            NSLog(@"%@:%@",phoneLabel,phoneNumber);
        }
        cell.textLabel.text = [NSString stringWithFormat:@"%@ %@",firstName,lastName];
        if (count > 0) {
            cell.detailTextLabel.text = (__bridge NSString *)(ABMultiValueCopyValueAtIndex(phoneNumbersRef, 0));
        }
        //使用cell的tag存储记录ID
        cell.tag = ABRecordGetRecordID(recordRef);
        //记录最后一个记录的ID
        if (indexPath.row == self.allPerson.count - 1) {
            self.lastID = ABRecordGetRecordID(recordRef);
        }
        return cell;
    }
    
    
    6. UI点击以及视图控制器初始化和销毁
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.tableView.delegate = self;
        self.tableView.dataSource = self;
        //请求访问通讯录并获取通讯录所有记录
        [self requestAddressBook];
    }
    //由于在整个视图控制器周期内addressBook都驻留在内存中,所以当控制器视图销毁时销毁该对象
    - (void)dealloc{
        if (self.addressBook != NULL) {
            CFRelease(self.addressBook);
        }
    }
    #pragma mark - UI点击
    - (IBAction)addPerson:(id)sender {
        //添加联系人
        [self addPersonWithFirstName:@"liu"
                            lastName:@"ting"
                          workNumber:@"13412321332"];
        //获取所有通讯录记录
        [self initAllPerson];
        //刷新表格
        [self.tableView reloadData];
    }
    - (IBAction)removePerson:(id)sender {
        //删除联系人
        [self removePersonWithName:@"liu ting"];
        //获取所有通讯录记录
        [self initAllPerson];
        //刷新表格
        [self.tableView reloadData];
    }
    - (IBAction)changePerson:(id)sender {
        [self modifyPersonWithRecordID:self.lastID
                             firstName:@"XXXX"
                              lastName:@"YYY"
                            workNumber:@"1111111111"];
        //获取所有通讯录记录
        [self initAllPerson];
        //刷新表格
        [self.tableView reloadData];
    }
    

    三、AddressBookUI

    AddressBookUI这个框架就提供了现成的控制器视图供开发者使用,高度封装化。

    下面是这个框架中提供的控制器视图:
    1. ABPersonViewController
      用于查看联系人信息(可设置编辑)。
      需要设置displayedPerson属性来设置要显示或编辑的联系人。
    2. ABNewPersonViewController
      用于新增联系人信息。
    3. ABUnknownPersonViewController
      用于显示一个未知联系人(尚未保存的联系人)信息。
      需要设置displayedPerson属性来设置要显示的未知联系人。
    4. ABPeoplePickerNavigationController
      用于选择联系人。

    前面三个控制器视图均继承于UIViewController,在使用过程中必须使用一个UINavigationController进行包装,否则只能看到视图内容无法进行操作,并且必须处理控制器的关闭操作,可以通过代理方法获得新增、修改的联系人。
    最后一个控制器视图继承于UINavigationController ,视图自身的“组”、“取消”按钮操作不需要开发者来完成,例如开发者不用在点击取消时关闭当前控制器视图,它自身已经实现了关闭方法。

    下面是这四个控制器的代理方法:
    #pragma mark - ABPersonViewController代理方法
    //选择一个人员属性后触发,返回值YES表示触发默认行为操作,否则执行代理中自定义的操作
    - (BOOL)personViewController:(ABPersonViewController *)personViewController
            shouldPerformDefaultActionForPerson:(ABRecordRef)person
                        property:(ABPropertyID)property
                      identifier:(ABMultiValueIdentifier)identifier;
    #pragma mark - ABNewPersonViewController代理方法
    /* 
        完成新增(点击取消和完成按钮时调用),注意这里不用做实际的通讯录增加工作,
        此代理方法调用时已经完成新增,当保存成功的时候参数中得person会返回保存的记录,如果点击取消person为NULL
     */
    - (void)newPersonViewController:(ABNewPersonViewController *)newPersonView
           didCompleteWithNewPerson:(ABRecordRef)person;
    #pragma mark - ABUnknownPersonViewController代理方法
    //保存未知联系人时触发
    - (void)unknownPersonViewController:(ABUnknownPersonViewController *)unknownCardViewController
                     didResolveToPerson:(ABRecordRef)person;
    #pragma mark - ABPeoplePickerNavigationController代理方法
    //选择一个联系人后调用,注意这个代理方法实现后选择属性的方法将不会再调用
    - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
                             didSelectPerson:(ABRecordRef)person;
    //选择属性之后调用,注意如果上面的代理方法实现后此方法不会被调用
    - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker 
                             didSelectPerson:(ABRecordRef)person 
                                    property:(ABPropertyID)property 
                                  identifier:(ABMultiValueIdentifier)identifier;
    //点击取消按钮调用
    - (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker;
    
    下面是具体代码示例【我包装了一个全局导航控制器】:
    #import "addressBookUIViewController.h"
    #import <AddressBookUI/AddressBookUI.h>
    
    @interface addressBookUIViewController ()  <ABNewPersonViewControllerDelegate,
                                                ABUnknownPersonViewControllerDelegate,
                                                ABPeoplePickerNavigationControllerDelegate,
                                                ABPersonViewControllerDelegate>
    
    @end
    
    @implementation addressBookUIViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    }
    
    #pragma mark - UI事件
    //点击添加联系人
    - (IBAction)addPersonClick:(UIButton *)sender {
        //创建添加联系人视图控制器
        ABNewPersonViewController *newPersonController = 
                      [[ABNewPersonViewController alloc] init];
        //设置代理
        newPersonController.newPersonViewDelegate = self;
        //注意必须有一层导航控制器才能使用,否则不会出现取消和完成按钮,无法进行保存等操作
        [self.navigationController pushViewController:newPersonController animated:YES];
    }
    //点击未知联系人
    - (IBAction)unknownPersonClick:(UIButton *)sender {
        //创建未知联系人视图控制器
        ABUnknownPersonViewController *unknownPersonController = 
                      [[ABUnknownPersonViewController alloc] init];
        //设置未知人员
        ABRecordRef recordRef=ABPersonCreate();
        ABRecordSetValue(recordRef, kABPersonFirstNameProperty, @"Kenshin", NULL);
        ABRecordSetValue(recordRef, kABPersonLastNameProperty, @"Cui", NULL);
        ABMultiValueRef multiValueRef = ABMultiValueCreateMutable(kABStringPropertyType);
        ABMultiValueAddValueAndLabel(multiValueRef, @"18500138888", kABHomeLabel, NULL);
        ABRecordSetValue(recordRef, kABPersonPhoneProperty, multiValueRef, NULL);
        unknownPersonController.displayedPerson = recordRef;
        //设置代理
        unknownPersonController.unknownPersonViewDelegate = self;
        //设置其他属性
        unknownPersonController.allowsActions = YES;//显示标准操作按钮
        unknownPersonController.allowsAddingToAddressBook = YES;//是否允许将联系人添加到地址簿
        //释放资源
        CFRelease(multiValueRef);
        CFRelease(recordRef);
        
        [self.navigationController pushViewController:unknownPersonController animated:YES];
    }
    //点击显示联系人
    - (IBAction)showPersonClick:(UIButton *)sender {
        //创建显示联系人视图控制器
        ABPersonViewController *personController = [[ABPersonViewController alloc] init];
        //设置联系人,取得id为1的联系人记录
        ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
        ABRecordRef recordRef = ABAddressBookGetPersonWithRecordID(addressBook, 1);
        personController.displayedPerson = recordRef;
        //设置代理
        personController.personViewDelegate = self;
        //设置其他属性
        personController.allowsActions = YES;//是否显示发送信息、共享联系人等按钮
        personController.allowsEditing = YES;//允许编辑
        
        [self.navigationController pushViewController:personController animated:YES];
    }
    //点击选择联系人
    - (IBAction)selectPersonClick:(UIButton *)sender {
        //创建选择联系人导航视图控制器
        ABPeoplePickerNavigationController *peoplePickerController =
                    [[ABPeoplePickerNavigationController alloc] init];
        //设置代理
        peoplePickerController.peoplePickerDelegate = self;
        //以模态弹出
        [self presentViewController:peoplePickerController animated:YES completion:nil];
    }
    
    #pragma mark - ABNewPersonViewController代理方法
    /* 
        完成新增(点击取消和完成按钮时调用),注意这里不用做实际的通讯录增加工作,
        此代理方法调用时已经完成新增,当保存成功的时候参数中得person会返回保存的记录,
        如果点击取消person为NULL
     */
    - (void)newPersonViewController:(ABNewPersonViewController *)newPersonView
           didCompleteWithNewPerson:(ABRecordRef)person
    {
        //如果有联系人信息
        if (person) {
            NSLog(@"%@ 信息保存成功.",(__bridge NSString *)(ABRecordCopyCompositeName(person)));
        }else{
            NSLog(@"点击了取消.");
        }
        //返回主视图窗口
        [self.navigationController popToRootViewControllerAnimated:YES];
        
    }
    #pragma mark - ABUnknownPersonViewController代理方法
    //保存未知联系人时触发
    - (void)unknownPersonViewController:(ABUnknownPersonViewController *)unknownCardViewController
                     didResolveToPerson:(ABRecordRef)person
    {
        if (person) {
            NSLog(@"%@ 信息保存成功!",(__bridge NSString *)(ABRecordCopyCompositeName(person)));
        }
        //返回主视图窗口
        [self.navigationController popToRootViewControllerAnimated:YES];
    }
    
    #pragma mark - ABPersonViewController代理方法
    //选择一个人员属性后触发,返回值YES表示触发默认行为操作,否则执行代理中自定义的操作
    - (BOOL)personViewController:(ABPersonViewController *)personViewController
            shouldPerformDefaultActionForPerson:(ABRecordRef)person
                        property:(ABPropertyID)property
                      identifier:(ABMultiValueIdentifier)identifier
    {
        if (person) {
            NSLog(@"选择了属性:%d",property);
            NSLog(@"值为:%@", (__bridge NSString *)ABRecordCopyValue(person, property));
        }
        return NO;
    }
    #pragma mark - ABPeoplePickerNavigationController代理方法
    //选择一个联系人后调用,注意这个代理方法实现后选择属性的方法将不会再调用
    - (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
                             didSelectPerson:(ABRecordRef)person
    {
        if (person) {
            NSLog(@"选择了%@.",(__bridge NSString *)(ABRecordCopyCompositeName(person)));
        }
    }
    //点击取消按钮后调用
    - (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker{
        NSLog(@"取消选择.");
    }
    @end
    




    代码Demo点这里:LearnDemo里面的AddressBookDemo

    如果有什么问题可以在下方评论区留言,我会积极响应的!O(∩_∩)O哈!
  • 相关阅读:
    我爬取了爬虫岗位薪资,分析后发现爬虫真香
    红薯,撑起父亲的快乐,让我揪心
    跨域问题服务端解决办法 Request header field Authorization is not allowed by Access-Control-Allow-Headers
    antdvue2.x 使用阿里iconfont自定义组件iconfont
    前端 crypto-js aes 加解密
    jsencrypt加密解密字符串
    CryptoJS base64使用方法
    客户端js生成rsa 密钥对
    js动态添加style样式
    PHP 使用非对称加密算法(RSA)
  • 原文地址:https://www.cnblogs.com/liutingIOS/p/5397623.html
Copyright © 2020-2023  润新知