25
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AddressBook / Contacts.frameworkを利用してアドレス帳のデータを利用する

Last updated at Posted at 2015-10-15

前書き

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に)
スクリーンショット 2015-10-15 12.15.57.png

参考資料

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する

AddressBook

@import AddressBook;
Contacts

@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以降
}

認証

まずは連絡先へのアクセスを許可してもらう必要があります。

AddressBook

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でも大枠は変わりません。

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型に変換しています。

AddressBook
- (ABAddressBookRef)createAddressBook {
    ABAddressBookRef addressBook = NULL;
    addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
    
    return addressBook;
}
AddressBook

ABAddressBookRef addressBook = [self createAddressBook];
NSArray* people = (NSArray *)CFBridgingRelease(ABAddressBookCopyArrayOfAllPeople(addressBook));

Contacts.frameworkではenumerateContactsWithFetchRequest:error:usingBlockを利用することで全てのデータを参照することができます。
usingBlockでは、ある特定のデータが見つかったら終了させることも可能です。その場合は、終了したいタイミングでstopにYESを指定してください。

unifiedContactsMatchingPredicate:keysToFetch:error:などを利用すれば指定したPredicateでフィルタされた情報を取得することも可能です。

Contacts
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を参考に書いてみます。

AddressBook
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オブジェクトとして利用できます。

Contacts
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を呼び出す形になります。

AddressBook
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する形になります。

Contacts
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またはsetContactupdateContactを行って最後にsaveを呼び出しても問題ないです。

更新対象のレコードがaRecordに入っていると仮定する

AddressBook
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に変わっているので注意

Contacts
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と幅広く共通で使えるようです。

25
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?