前書き
AddressBook.frameworkがiOS9からdeprecatedになるというのは
良くご存知のことだと思います。
仕方がないのでAddressBookを使っていた箇所をContactsに置き換えたので、メモ書き程度に。
AddressBookでの記述方法と、Contactsでの記述方法を交互に書いていきます。
注意事項
・Contacts.frameworkはiOS9から対応のものになります。
・当然ながらiOS8以前ではAddressBook.frameworkを利用する必要があります。
・ContactsUI.frameworkは今回扱いません
・私はまだswift使ってないので、Objective-Cです。swiftなら検索すれば出てきます。
・他言語対応とかは特に考えてません。(full Name周りとか)
・iOS8以前でもアプリが動作できるように、frameworkはoptionalで追加してください。(AddressBook.frameworkもOptionalに)
参考資料
https://developer.apple.com/library/ios/documentation/Contacts/Reference/Contacts_Framework/index.html
https://developer.apple.com/library/watchos/documentation/Contacts/Reference/CNContactStore_Class/index.html
準備
・Contacts.frameworkをOptionalで追加する
・Contacts.frameworkをimportする
@import AddressBook;
@import Contacts;
処理の切り分け方
私は、以下のようなマクロを定義して普通にif文で切り分けています。
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
// 使い方
if (SYSTEM_VERSION_LESS_THAN(@"9.0")) {
// iOS9未満
} else {
// iOS9以降
}
認証
まずは連絡先へのアクセスを許可してもらう必要があります。
ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();
switch (status) {
case kABAuthorizationStatusNotDetermined:
case kABAuthorizationStatusRestricted:
{
NSLog(@"Not determined or restricted.");
ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
if (granted) {
// 利用可能
} else {
// アクセス拒否
}
});
}
break;
case kABAuthorizationStatusDenied:
{
// アクセス拒否状態
}
break;
case kABAuthorizationStatusAuthorized:
{
// 利用可能
}
break;
default:
break;
}
Contactsでも大枠は変わりません。
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
switch (status) {
// まだダイアログから選択を行っていない
case CNAuthorizationStatusNotDetermined:
// ペアレンタルコントロールや、機能制限によりアクセス不可
case CNAuthorizationStatusRestricted:
{
CNContactStore *store = [CNContactStore new];
[store requestAccessForEntityType:CNEntityTypeContacts
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
// 利用可能
} else {
// 失敗(拒否された)
}
}];
}
break;
// 拒否が選択されている
case CNAuthorizationStatusDenied:
break;
// 利用可能
case CNAuthorizationStatusAuthorized:
// 利用可能
break;
default:
break;
}
全件取得
AddressBookでは、ABAddressBookCopyArrayOfAllPeople
を利用することで
全ての要素を取得することができます。
戻り値もCFArrayRef<ABRecordRef>
になりますが、処理しやすいようにCFBridgingRelease
でNSArray型に変換しています。
- (ABAddressBookRef)createAddressBook {
ABAddressBookRef addressBook = NULL;
addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
return addressBook;
}
ABAddressBookRef addressBook = [self createAddressBook];
NSArray* people = (NSArray *)CFBridgingRelease(ABAddressBookCopyArrayOfAllPeople(addressBook));
Contacts.frameworkではenumerateContactsWithFetchRequest:error:usingBlock
を利用することで全てのデータを参照することができます。
usingBlockでは、ある特定のデータが見つかったら終了させることも可能です。その場合は、終了したいタイミングでstopにYESを指定してください。
unifiedContactsMatchingPredicate:keysToFetch:error:
などを利用すれば指定したPredicateでフィルタされた情報を取得することも可能です。
CNContactStore *store = [CNContactStore new];
NSError *error;
// KeysToFetchには、利用したいアドレス帳の情報を指定します
// 今回は、苗字、名前、ふりがな、電話番号、ノートにしています。
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactNoteKey,CNContactPhoneNumbersKey]];
NSMutableArray *people = @[].mutableCopy;
BOOL success = [store enumerateContactsWithFetchRequest:request error:&error
usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
// 全て追加
[people addObject:contact];
}];
if (success) {
// 全件取得成功
} else {
NSLog(@"%s %@",__func__, error);
}
レコードの利用方法
ABAddressBookでは、取得できる要素がCFオブジェクトなので使い方にかなりクセがあります。おまけに解放を忘れると、すぐにリークするのでAnalyzeは忘れずに。
上で取得したpeopleのArrayを参考に書いてみます。
ABRecordRef aRecord = (__bridge ABRecordRef)(people[0]);
// 名前
NSString *firstName = (__bridge NSString *)(ABRecordCopyValue(record, kABPersonFirstNameProperty));
NSString *lastName = (__bridge NSString *)(ABRecordCopyValue(record, kABPersonLastNameProperty));
// 電話番号
ABMultiValueRef tels = ABRecordCopyValue(record, kABPersonPhoneProperty);
int count = (int)ABMultiValueGetCount(tels); // 件数
for (int i = 0; i < count ; i++) {
// 1件ずつの内容
NSString *number = (__bridge NSString *)(ABMultiValueCopyValueAtIndex(mutableValue, i);
}
Contactsではこの辺りが改善されていて、普通にNSオブジェクトとして利用できます。
CNContact *contact = people[0];
// 名前
contact.givenName;
contact.familyName;
// 電話番号
for (CNLabeledValue *label in contact.phoneNumbers) {
// 1件ずつの内容
NSString *number = ((CNPhoneNumber *)label.value).stringValue;
}
新規追加
ABAddressBookでは、1件ずつレコードを作成してAddressBookに追加。
最後にABAddressBookSave
を呼び出す形になります。
ABAddressBookRef addressBook = [self createAddressBook];
ABRecordRef aRecord = ABPersonCreate();
CFErrorRef anError = nil;
// 名前とふりがな
// 名前登録
if (!ABRecordSetValue(aRecord, kABPersonFirstNameProperty, (__bridge CFTypeRef)(@"名前"), &anError)) {
// エラー
NSLog(@"firstName error %@",anError);
}
// 姓登録
if (!ABRecordSetValue(aRecord, kABPersonLastNameProperty, (__bridge CFTypeRef)(@"苗字"), &anError)) {
NSLog(@"lastName error %@",anError);
}
// 名前ふりがな
if (!ABRecordSetValue(aRecord, kABPersonFirstNamePhoneticProperty, (__bridge CFTypeRef)(@"なまえ"), &anError)) {
// エラー
NSLog(@"firstRuby error %@",anError);
}
// 姓ふりがな
if (!ABRecordSetValue(aRecord, kABPersonLastNamePhoneticProperty, (__bridge CFTypeRef)(@"みょうじ"), &anError)) {
NSLog(@"lastRuby error %@",anError);
}
// 電話番号
ABMutableMultiValueRef phone = ABMultiValueCreateMutable(kABMultiStringPropertyType);
ABMultiValueAddValueAndLabel(phone, (__bridge CFTypeRef)(@"080-1234-5678"), kABPersonPhoneMainLabel, NULL);
if (!ABRecordSetValue(aRecord, kABPersonPhoneProperty, phone, &anError)) {
NSLog(@"tel error %@",anError);
}
// ノート
CFStringRef memo = CFBridgingRetain(@"メモ");
if (!ABRecordSetValue(aRecord, kABPersonNoteProperty, memo, &anError)) {
NSLog(@"Note error %@",anError);
}
ABAddressBookAddRecord(addressBook, aRecord, &anError);
CFRelease(memo);
// 保存
ABAddressBookSave(addressBook, &anError);
Contactsでは、保存用のRequestを最後にexecuteSaveRequest:error
する形になります。
CNContactStore *store = [CNContactStore new];
CNSaveRequest *request = [CNSaveRequest new];
CNMutableContact *contact = [CNMutableContact new];
// 名前登録
[contact setGivenName:@"名前"];
[contact setFamilyName:@"苗字"];
[contact setPhoneticGivenName:@"なまえ"];
[contact setPhoneticFamilyName:@"みょうじ"];
// 電話番号登録
NSMutableArray *phone = @[].mutableCopy;
[phone addObject:[[CNLabeledValue alloc] initWithLabel:CNLabelPhoneNumberMain value:[CNPhoneNumber phoneNumberWithStringValue:@"080-1234-5678"]]];
[contact setPhoneNumbers:phone];
// ノート
[contact setNote:@"メモ"];
// requestに追加
[request addContact:contact toContainerWithIdentifier:[store defaultContainerIdentifier]];
// 保存
NSError *error;
if (![store executeSaveRequest:request error:&error]) {
NSLog(@"%s %@",__func__, error);
}
更新
ちなみに、追加と更新は別に分ける必要はないのでまとめてにABAddressBookAddRecord
またはsetContact
、updateContact
を行って最後にsaveを呼び出しても問題ないです。
更新対象のレコードがaRecordに入っていると仮定する
ABAddressBookRef addressBook = [self createAddressBook];
ABRecordRef aRecord = (__bridge ABRecordRef)(result[0]);
CFErrorRef anError = nil;
// 名前登録
if (!ABRecordSetValue(aRecord, kABPersonFirstNameProperty, (__bridge CFTypeRef)(@"名前更新"), &anError)) {
// エラー
NSLog(@"firstName error %@",anError);
}
// 電話番号登録
// 既存の情報をコピーする
ABMultiValueRef value = ABRecordCopyValue(aRecord, kABPersonPhoneProperty);
ABMutableMultiValueRef phone = ABMultiValueCreateMutableCopy(value);
ABMultiValueAddValueAndLabel(phone, (__bridge CFTypeRef)(@"090-1111-2222"), kABPersonPhoneMainLabel, NULL);
if (!ABRecordSetValue(aRecord, kABPersonPhoneProperty, phone, &anError)) {
NSLog(@"tel error %@",anError);
}
CFStringRef memo = CFBridgingRetain(@"更新");
if (!ABRecordSetValue(aRecord, kABPersonNoteProperty, memo, &anError)) {
NSLog(@"Note error %@",anError);
}
ABAddressBookAddRecord(addressBook, aRecord, &anError);
CFRelease(memo);
ABAddressBookSave(addressBook, &anError);
Contactsの場合、setContactがupdateContactに変わっているので注意
CNContactStore *store = [CNContactStore new];
CNSaveRequest *request = [CNSaveRequest new];
CNMutableContact *contact = [CNMutableContact new];
// 名前登録
[contact setGivenName:@"名前"];
[contact setFamilyName:@"苗字"];
[contact setPhoneticGivenName:@"なまえ"];
[contact setPhoneticFamilyName:@"みょうじ"];
// 電話番号登録
NSMutableArray *phone = contact.phoneNumbers.mutableCopy;
[phone addObject:[[CNLabeledValue alloc] initWithLabel:CNLabelPhoneNumberMain value:[CNPhoneNumber phoneNumberWithStringValue:@"080-1111-2222"]]];
[contact setPhoneNumbers:phone];
// ノート
[contact setNote:@"更新"];
// requestに追加
[request updateContact:contact];
// 保存
NSError *error;
if (![store executeSaveRequest:request error:&error]) {
NSLog(@"%s %@",__func__, error);
}
あとがき
ほんと、CFオブジェクトがNSオブジェクトになっただけでもかなり楽になりました。
AddressBook.frameworkはiOS限定のframeworkでしたが、
Contacts.frameworkはiOS9、Mac、watchOS2と幅広く共通で使えるようです。