AddressBook.framework引导


title: AddressBook.framework
date: 2016-08-22 17:44:14
tags: Frameworks
thumbnail: http://www.51wendang.com/pic/00a226f3c9fa6ed45d1d781c/1-817-png_6_0_0_300_110_360_270_892.5_1263-1200-0-0-1200.jpg


AddressBook.framework/AddressBookUI.framework

9.0之后, AddressBook.frameworkContacts.framework代替。但是目前大部分的应用软件起支撑的版本是iOS6.0或7.0, 所以AddressBook还大有用处。之前在的文章AddressBook, AddressBookUI中有提及, 但是由于是转载, 所以代码不是很清晰, 而且有一些读者希望得到清晰的代码以及详细的功能解释, 所以在这里把AddressBook.framework以及AddressBookUI.framework重新做一下详细的使用方法介绍。

这篇文章先介绍AddressBook.frameworkAddressBookUI以及Contacts.framework也会补上。

开始使用AdressBook

首先, 导入我们需要的Framework

#import 

经常使用到:

  • ABAddressBookRef: 通讯录引用
  • ABRecordRef: 记录引用
  • ABPropertyID: 记录的属性ID

获取AddressBook使用权限

像使用相机、推送一样, 访问AddressBook同样需要获取权限。

在获取权限之前,我们需要创建一个AddressBook的引用,用来做后续操作。

static ABAddressBookRef r;

static inline ABAddressBookRef getAddressBookRef() {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        CFErrorRef errorRef;
        r = ABAddressBookCreateWithOptions(NULL, &errorRef);
        if ( errorRef ) {
            ILog(@"%@", (__bridge NSString *)CFErrorCopyFailureReason(errorRef));
        }
    });
    return r;
}

ABAddressBookRef,作为后续使用的通讯录引用,这里写作单例。或者可以写作单例的属性,但只需初始化一次。

ABAddressBookCreateWithOptions通过参数创建, options暂时为预留字段,但是AddressBook在9.0被弃用,估计不会被使用了。

在获取到引用之后,我们应先查询AddressBook的访问状态:

Boolean needRequestAccess() {
    ABAuthorizationStatus s = ABAddressBookGetAuthorizationStatus();
    if ( s == kABAuthorizationStatusDenied ||
        s == kABAuthorizationStatusRestricted ) {
            alert(@"提示", @"");
        return false;
    }
    if ( s != kABAuthorizationStatusAuthorized ) {
        return true;
    }
    return false;
}

推荐使用switch-case来判断状态。如果结果为kABAuthorizationStatusAuthorized表示可以正常访问。要注意的是,如果用户拒绝了首次的请求,那么需要用户在设置-隐私-通讯录中手动打开App使用通讯录的权限。

如果结果为kABAuthorizationStatusNotDetermined意味着我们需要请求访问权限:

Restricted意味着系统决定了访问权限,用户不能修改。

void requestAddressBookAccess (ABAddressBookRequestAccessCompletionHandler handler) {
    ABAddressBookRequestAccessWithCompletion(getAddressBookRef(), handler);
}

综合起来的调用:

void initAddressBook (void (^shouldAccessAddressBook)(Boolean boo)) {
    if ( needRequestAccess() ) {
        requestAddressBookAccess(^(bool granted, CFErrorRef error) {
            if ( granted ) {
                shouldAccess = true;
            }
            shouldAccessAddressBook(granted);
        });
    } else {
        shouldAccess = true;
        shouldAccessAddressBook(true);
    }
}

非常简单的,我们获得了通讯录的访问权限。

用户的查询

用户的查询非常简单;

  • ABAddressBookGetPersonCount:获取总人数
  • ABAddressBookGetPersonWithRecordID:通过RecordID获取单个人,recordID可以通过ABRecordGetRecordID获得
  • ABAddressBookCopyArrayOfAllPeople:获取全部联系人数组
  • ABAddressBookCopyArrayOfAllPeopleInSource:获取记录引用中所有的练习嗯
  • ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering:带着排序参数
  • ABAddressBookCopyPeopleWithName:拷贝符合该名称的联系人

获取联系人的数量:

CFIndex i = ABAddressBookGetPersonCount(getAddressBookRef());
printf("AddressBook: has %ld Person", i);

获取联系人:

CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef());
    
ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0);
ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty));

获取联系人组使用ABAddressBookCopyArrayOfAllGroups即可。然后通过ABAddressBookCopyArrayOfAllPeopleInSource即可获得组内联系人。

用户信息的修改与删除

CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef());
    
ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0);
ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty));
    
if ( ABRecordSetValue(pr, kABPersonFirstNameProperty, (__bridge CFStringRef)@"Hello", nil) ) {
    if ( ABAddressBookSave(getAddressBookRef(), nil) ) {
        ILog(@"Succeed!");
    }
}
    
if ( ABAddressBookRemoveRecord(getAddressBookRef(), pr, nil) ) {
    if ( ABAddressBookSave(getAddressBookRef(), nil) ) {
        ILog(@"Remove Succeed!");
    }
}

ABRecordCopyValue用来通过ABPropertyID获取相应属性的内容, 具体的ID在ABPerson.h中有详细列表。

ABRecordSetValue用来通过ABPropertyID设置相应的属性内容,同时返回bool值以供判段。参数中的error已经不在使用。

在修改后,切记保存修改,使用ABAddressBookSave做保存,在这之前,可以使用ABAddressBookHasUnsavedChanges判断是否存在未保存的修改。

使用ABAddressBookRemoveRecord来移除记录。

监听其他应用对AddressBook的修改

在操作AddressBook的同时,有可能在后台的时候被其他程序所修改,addressBook提供了监听方法:

void callBack(ABAddressBookRef addressBook, CFDictionaryRef info, void *context)  {
    ILog(@"AddressBook has changed in another application.");
};

void handleChange() {
    ABAddressBookRegisterExternalChangeCallback(getAddressBookRef(), callBack, nil);
}

在收到监听后我们需要做相应的处理,比如: 是否其他改动与我们的改动有重叠等。

注意点

  • 在设置值的时候,可以使用ABMultiValueRef设置多个值,比如说多个电话。
  • 在不使用的时候,要使用ABAddressBookUnregisterExternalChangeCallback取消对AddressBook的监听。

Q&A:

Q:如何在删除完联系人的多个电话后,直接删除联系人?

A:通过propertyID获取到电话的信息后,做简单的判断就可以实现。如果仅存这一条电话记录,那么在删除的同时也删除掉联系人即可。

本文全部的代码

#import "Ins_AddressBook.h"

@implementation InsAddressBook

static Boolean shouldAccess;
static ABAddressBookRef r;

static inline ABAddressBookRef getAddressBookRef() {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        CFErrorRef errorRef;
        r = ABAddressBookCreateWithOptions(NULL, &errorRef);
        if ( errorRef ) {
            ILog(@"%@", (__bridge NSString *)CFErrorCopyFailureReason(errorRef));
        }
    });
    return r;
}

void requestAddressBookAccess (ABAddressBookRequestAccessCompletionHandler handler) {
    ABAddressBookRequestAccessWithCompletion(getAddressBookRef(), handler);
}

Boolean needRequestAccess() {
    ABAuthorizationStatus s = ABAddressBookGetAuthorizationStatus();
    if ( s == kABAuthorizationStatusDenied ||
        s == kABAuthorizationStatusRestricted ) {
            alert(@"提示", @"你之前已经拒绝了程序的访问权限, 请在设置-隐私-通讯录中手动打开, 并重新启动应用。");
        return false;
    }
    if ( s != kABAuthorizationStatusAuthorized ) {
        return true;
    }
    return false;
}

void initAddressBook (void (^shouldAccessAddressBook)(Boolean boo)) {
    if ( needRequestAccess() ) {
        requestAddressBookAccess(^(bool granted, CFErrorRef error) {
            if ( granted ) {
                shouldAccess = true;
            }
            shouldAccessAddressBook(granted);
        });
    } else {
        shouldAccess = true;
        shouldAccessAddressBook(true);
    }
}

void callBack(ABAddressBookRef addressBook, CFDictionaryRef info, void *context)  {
    ILog(@"AddressBook has changed in another application.");
};

void handleChange() {
    ABAddressBookRegisterExternalChangeCallback(getAddressBookRef(), callBack, nil);
}

void getPerson () {
    CFIndex i = ABAddressBookGetPersonCount(getAddressBookRef());
    printf("AddressBook: has %ld Person", i);
    CFArrayRef ref = ABAddressBookCopyArrayOfAllPeople(getAddressBookRef());
    
    ABRecordRef pr = CFArrayGetValueAtIndex(ref, 0);
    ILog(@"%@", ABRecordCopyValue(pr, kABPersonFirstNameProperty));
    
    if ( ABRecordSetValue(pr, kABPersonFirstNameProperty, (__bridge CFStringRef)@"Hello", nil) ) {
        if ( ABAddressBookSave(getAddressBookRef(), nil) ) {
            ILog(@"Succeed!");
        }
    }
    
    if ( ABAddressBookRemoveRecord(getAddressBookRef(), pr, nil) ) {
        if ( ABAddressBookSave(getAddressBookRef(), nil) ) {
            ILog(@"Remove Succeed!");
        }
    }
}

- (instancetype)init {
    self = [super init];
    if ( self ) {
        initAddressBook(^(Boolean boo){
            if ( boo ) {
                ILog(@"Get access to addressBook")
                handleChange();
                getPerson();
            } else {
                ILog(@"Without access to addressBook")
            }
        });
    }
    return self;
}

DEF_SINGLETON_AUTOLOAD(InsAddressBook)

@end

AddressBookUI.framework

相比而言,使用UI则简单的多,直接进入创建一个新的用户:

ABNewPersonViewController * pv = [[ABNewPersonViewController alloc] init];
pv.newPersonViewDelegate = self;
[getRootNc() presentViewController:INS_NAV(pv) animated:YES completion:nil];
- (void) newPersonViewController: (ABNewPersonViewController *) newPersonView
        didCompleteWithNewPerson: (ABRecordRef) person {
    [newPersonView.navigationController dismissViewControllerAnimated:YES completion:nil];
}

创建界面与其代理方法,当然在ABNewPersonViewController中,有写参数我们可以设置,意义很简单,可以通过我们上边AddressBook.framework中获得的一些引用穿进去,或者在创建用户之前直接设置一个用户的基础信息。这里不做赘述。

选择一个用户

ABPeoplePickerNavigationController * pv = [[ABPeoplePickerNavigationController alloc] init];
pv.peoplePickerDelegate = self;
[getRootNc() presentViewController:pv animated:YES completion:nil];
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController*)peoplePicker didSelectPerson:(ABRecordRef)person {
    
}

// Called after the user has pressed cancel.
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
    [peoplePicker dismissViewControllerAnimated:YES completion:nil];
}

注:这里我没有低版本的测试环境,但是可以看到,选中人的方法在8.0后才被使用,所以低版本的童鞋应该主动尝试原本被弃用的2个方法。

展示用户

ABPersonViewController

If displayedPerson has been added to an ABAddressBook, then the addressBook property will be updated to use the displayedPerson's ABAddressBook.

可以设置的属性包括:允许编辑、允许操作(短信、邮件等),允许展示连接的联系人,设置属性高亮等。也比较方便。

信息的完善

ABUnknownPersonViewController

ABUnknownPersonViewController *un = [[ABUnknownPersonViewController alloc] init];
un.displayedPerson = person; // 展示的联系人
un.allowsAddingToAddressBook = YES; // 允许添加到通讯录中
[getRootNc() presentViewController:INS_NAV(un) animated:YES completion:nil];

总结

AddressBook.framework、AdressBookUI.framework还是可以满足一些基本需求,但是由于是c库,并且功能不是很完善,所以在iOS9.0之后苹果使用Contacts.framework来代替AddressBook.framework。ContactsFramework是一整套OC的库,理解起来也很简单。

这是ContactsFramework中包含的一些头文件,在使用AddressBook的时候,基本所有的方法都在后边写了使用ContactsFramework中什么方法来代替。

CNContact以及CNGroup分别代表了联系人与联系人组,比之前的Record引用清晰了许多。

CNContactFetchRequest以及CNSaveRequest方便的提供了查询以及保存等操作。

CNSaveRequest则提供了方便直观的方法去保存用户。

然后整个框架都清晰了很多,基本的使用方法如下:

CNContactStore * c;
CNAuthorizationStatus s;
CNContactFetchRequest * f;
NSError * e;
    
s = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
c = [[CNContactStore alloc] init];
    
if ( s != CNAuthorizationStatusAuthorized ) {
        ILog(@"Un Authorized !");
        
        [c requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * error) {
            
        }];
}
    
    // 根据`CNContact`中的属性单独获得,比如 @[CNContactGivenNameKey, CNContactMiddleNameKey, ...]
f = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactMiddleNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey]];
    
BOOL b = [c enumerateContactsWithFetchRequest:f error:&e usingBlock:^(CNContact * contact, BOOL * stop) {
        
        /*
         !$_, value=>"
         ), emailAddresses=(
         "!$_, [email protected]>"
         ), postalAddresses=(not fetched)>
         */
}];
    
if ( b ) {
        ILog(@"Search success.");
}

当然不能所有的代码全部我贴出来,关于保存等功能,大家自行探索。

2016-8-23 上午10:00 copyRight@[email protected] 欢迎转载。

你可能感兴趣的:(AddressBook.framework引导)