MusicKitでApple Musicと連携する実装を終えたところで、アプリ内で連絡先を選択できるようにしたいと考え、Contactsフレームワークによる連絡先アクセスに挑戦しました。
1. 権限が許可されている前提でコードを書いていた
最初、権限リクエストさえ呼んでおけば、その後の処理はすべて許可された状態で進むものだと思い込んでいました。
let store = CNContactStore()
store.requestAccess(for: .contacts) { granted, error in
}
このコールバックのgrantedがfalseになるケース、つまりユーザーが拒否した場合の処理をきちんと書いておらず、拒否時にアプリがそのままフリーズしたような見た目になってしまいました。許可・拒否の両方の分岐を用意し、拒否時には代替手段(手動入力など)を案内する必要があると分かりました。
2. フルネームを直接取得できるプロパティがあると思い込んでいた
連絡先の名前を表示する際、フルネームを1つのプロパティから取得できるものだと考えていました。
let fullName = "\(contact.givenName) \(contact.familyName)"
このように単純に姓名を連結すると、名前の表記順序が文化圏によって異なる場合に不自然な表示になってしまうことに気づきました。調べてみると、CNContactFormatterという専用のフォーマッタが用意されており、これを使うことでロケールに応じた適切な表示形式に整形できると分かりました。
3. 取得する項目を指定しないと重い処理になった
連絡先一覧を取得する処理を書いたところ、件数が多い環境で動作が遅くなる問題に気づきました。
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey] as [CNKeyDescriptor]
let request = CNContactFetchRequest(keysToFetch: keys)
取得したいプロパティ(キー)を明示的に指定せずに全項目を取得しようとしていたことが原因でした。本当に必要なキーだけをkeysToFetchに指定することで、無駄なデータ取得を減らし、動作を軽くすることができました。
4. 連絡先アプリ側での変更に気づけなかった
アプリ内にキャッシュした連絡先情報が、ユーザーが標準の連絡先アプリで情報を編集した後も古いまま表示され続ける問題がありました。
NotificationCenter.default.addObserver(
forName: .CNContactStoreDidChange, object: nil, queue: .main
) { _ in
}
CNContactStoreDidChangeという通知を購読することで、連絡先データが変更されたタイミングを検知できることが分かりました。この通知を受け取ったタイミングでキャッシュを再取得する処理を追加し、常に最新の情報を表示できるようにしました。
まとめ
- 権限リクエストの結果が拒否だった場合の分岐を必ず用意し、代替手段を案内する
- 姓名の連結表示は
CNContactFormatterを使い、ロケールに応じた自然な表示にする - 取得するプロパティ(キー)を必要な分だけ指定し、無駄な取得処理を避ける
CNContactStoreDidChange通知を購読することで、外部での連絡先変更を検知できる
普段何気なく使っている連絡先という情報も、権限・国際化・同期という3つの観点を意識して扱う必要があると実感した実装でした。
本記事は生成AI(Claude)を活用して執筆・編集しています。内容は実体験に基づき、執筆者本人が確認・監修しています。


コメント