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

## 前書き
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://qiita-image-store.s3.amazonaws.com/0/34166/e9cc7ac0-79fa-a818-2800-199fc254624c.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する

```objc:AddressBook

@import AddressBook;
```

```objc:Contacts

@import Contacts;
```

## 処理の切り分け方

私は、以下のようなマクロを定義して普通にif文で切り分けています。

```objc
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)

// 使い方
if (SYSTEM_VERSION_LESS_THAN(@"9.0")) {
    // iOS9未満
} else {
    // iOS9以降
}
```

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

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

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

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

```objc:AddressBook

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

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

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

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

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

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

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

```objc: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`または`setContact`、`updateContact`を行って最後にsaveを呼び出しても問題ないです。


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

```objc: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**に変わっているので注意

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