【Objective-C】Contact.frameworkを使ってiPhone/iPad内の連絡先を取得する方法(ABAdressから移行します)【Xcode11/iOS13対応】

こういう人に向けて発信しています。
・ABAdress.frameworkの互換性がiOS9で切れて連絡先を取得したい人
・Contact.frameworkを取得したい人
・Objective-c中級者

なぜContact.frameworkに変える必要があるのか

(1)iOS2.0-9.0サポートのABAdressですが、
iOS13では取得する時にエラーが起きて落ちます。
今回はそのために対応していきます。

必要な準備

contact.frameworkを導入してください。
公式フレームワークの導入は調べてみてくださいね。

コード(Objective-c)

#import <Contacts/Contacts.h>
/*解釈:ABAdress.Frameworkでは全てのソース(ローカル、EXChange,iCloud)を取得していたので、ソース分だけfor構文を回して、
    ローカル(iOSデフォルト連絡先データ)をswitchで特定して処理していた。
    
    一方、Contact.Frameworkは containersMatchingPredicateに引数nilを渡す事で全部のコンテナを取得する。
    (1)端末上に入っている全てのcontainerを取得する。(例:ローカル、EXChange,iCloud)
    (2)container.idを元にそのコンテナに入っているgroupを入る。
    (3)コンテナidを指定して、そのコンテナの中に入っている全グループを連絡先を取得する。
    
    <仮説>
    - Group (例:worl,Friendなど)
      - CNContainer…コンテナ(ローカル、EXChange,iCloud)
       - Contact  …1件の連絡先データ
    */
   
   CNContactStore *store = [[CNContactStore alloc] init];
   [store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
       //成功時
       if (!granted) {
           dispatch_async(dispatch_get_main_queue(), ^{});
           return;
       }
       
       
       NSError *fetchError;
       NSArray *allKey = @[[CNContactVCardSerialization descriptorForRequiredKeys]];
       //全部のCNContainerを取得する。(ローカル,EXChange,iClouldなどでコンテナが分割される)
       NSArray *allContainerArray = [store containersMatchingPredicate:nil error:&fetchError];
       
       for(CNContainer *container in allContainerArray){
           //連絡先情報取得時に通る
           switch (container.type) {
               case CNContainerTypeUnassigned:
                   break;
                   
                   //ローカルソース(iPhoneデフォルト連絡先)
               case CNContainerTypeLocal:{
                   //上記コンテナに属する全グループを取得する
                   NSPredicate *predicate = [CNGroup predicateForGroupsInContainerWithIdentifier:container.identifier];
                   NSArray *cnGroups = [store groupsMatchingPredicate:predicate error:&error];
                   
                   //グループごとにvCardを分ける
                   for(CNGroup *group in cnGroups){
                       //もし件数が0件の時は処理を飛ばす
                       //if(!group || group == 0)continue;
                       NSPredicate *contactPredicate = [CNContact predicateForContactsInGroupWithIdentifier:group.identifier];
                       NSArray *contactArray = [store unifiedContactsMatchingPredicate:contactPredicate keysToFetch:allKey error:&error];
                       if(!contactArray || contactArray.count == 0) continue;
                       NSLog(@"aa");
                   }
                   
               }

Container、Group、contactについて

・Container(コンテナ)

     :typedef NS_ENUM(NSInteger, CNContainerType)
   {
       CNContainerTypeUnassigned = 0,
       CNContainerTypeLocal,
       CNContainerTypeExchange,
       CNContainerTypeCardDAV
   } NS_ENUM_AVAILABLE(10_11, 9_0);

(1)ローカル内のデフォルト連絡先コンテナ
(2)EXChangeなどの外部から同期した連絡先コンテナ
(3)iCloudで同期した連絡先コンテナ

※これで合っているはず!! 間違っていたら教えて欲しいです。

CLContainer型のプロパティ(container.type)を活用すれば
どこのコンテナなのか判別可能です。

・Group(グループ)

iPhone内で「Work」「Friends」などのあれです。

・Contact(連絡先)

最小単位です。

補足:store.defaultsContainerIdentifierは安易には使わない方がいいかも。

CNContactStore *store = [[CNContactStore alloc] init];
NSString *containerId = store.defaultContainerIdentifier;

上記のようにデフォルトコンテナの識別子を取得しようという記述ですが、
Exchangeが紐づけられている場合はExchangeのコンテナを示されることがありました。
追記)何故かというとiPhone内で下記記事で確認できるような「デフォルトのアカウント」を指定できます。

https://iphone-itunes.info/%E5%9F%BA%E6%9C%ACapp/contacts/4407

デフォルト=ローカルコンテナではないので注意しましょう。

NS_CLASS_AVAILABLE(10_11, 9_0)
@interface CNContainer : NSObject <NSCopying, NSSecureCoding>

/*! The identifier is unique among containers on the device. It can be saved and used for fetching containers next application launch. */
@property (readonly, copy, NS_NONATOMIC_IOSONLY) NSString *identifier;

@property (readonly, copy, NS_NONATOMIC_IOSONLY) NSString *name;
@property (readonly, NS_NONATOMIC_IOSONLY) CNContainerType type;

@end

typeからローカルコンテナかどうか、
identiferなども取得可能なので高速列挙などで
コンテナ全件からローカルコンテナを取り出して定数とする方が良いかもしれません。

この記事が気に入ったらサポートをしてみませんか?