ハイライト
iOS 18 は連絡先認可モデルに根本的な変更を導入:Limited Access(制限付きアクセス)。ユーザーは「許可」と「拒否」の 2 択だけではなく、一部の連絡先のみ共有を選択可能。認可ダイアログは 2 段階——第一段階でアクセス許可を尋ね、第二段階で共有する連絡先を選択、または全件開放。
主要内容
以前、app が連絡先権限を要求すると、ユーザーは「全部かゼロか」の選択に直面——連絡先全体を渡すか、何も渡さないか。ユーザーにとって難しい決断。チャット app は 3〜5 人の連絡先だけ必要かもしれないが、そのために全連絡先を開放しなければならなかった。多くのユーザーがこの選択のために「拒否」を押した。
iOS 18 がこれを変える。連絡先認可は Limited Access(制限付きアクセス)をサポート:第一段階のダイアログで連絡先共有を確認、第二段階で共有する特定の連絡先を選択、または全件開放。確認ページは選択した連絡先を表示し、後から変更可能と通知。認可レベルは 3 種(notDetermined / authorized / denied)から 4 種に拡張。.limited が追加。limited 状態では app は認可された連絡先のみ読み取り可能だが、変更・作成は可能。
この新モデルに対応し、Apple は 2 つの API を提供。ContactAccessPicker は全画面モーダル選択器。連絡先一括追加向け(ソーシャル app の友達マッチングなど)。ContactAccessButton はより巧妙——SwiftUI コンポーネントで検索結果リストに埋め込み。app がまだアクセス権を持たない一致連絡先を表示。ユーザーはワンタップで認可。app が notDetermined 状態でも使用可能——タップで簡略化された認可要求を自動表示。ユーザーが特定の連絡先を検索している瞬間に要求が発生するため、app がなぜ権限を必要とするか明確。認可率は自然に高くなる。
詳細
4 つの認可レベル
iOS 18 連絡先認可には 4 つの状態(00:33):
- Full Access:任意の連絡先を読み書き
- Limited Access:ユーザーが選択した連絡先のみ読み取り。変更・作成は可能
- Not Determined:未認可。CNContactStore にアクセスすると自動で認可ダイアログ
- Denied:連絡先データを読み書き不可
ContactAccessButton の基本的な使い方
公式サンプル。ContactAccessButton を検索結果リストに埋め込む方法(05:15):
@Binding var searchText: String
@State var authorizationStatus: CNAuthorizationStatus = .notDetermined
var body: some View {
List {
ForEach(searchResults(for: searchText)) { person in
ResultRow(person)
}
if authorizationStatus == .limited || authorizationStatus == .notDetermined {
ContactAccessButton(queryString: searchText) { identifiers in
let contacts = await fetchContacts(withIdentifiers: identifiers)
dismissSearch(withResult: contacts)
}
}
}
}
キーポイント:
searchTextは検索フィールド入力にバインド。Button は連絡先マッチに使用.limitedまたは.notDetermined状態でのみ表示——full access では不要、denied では無意味- コールバック引数
identifiersは新たに認可された連絡先識別子の[String] - identifiers 取得後、
CNContactStoreで完全な連絡先データを取得
外観カスタマイズ
ContactAccessButton は標準 SwiftUI modifier と 2 つの専用 modifier をサポート(06:10):
ContactAccessButton(queryString: searchText)
.font(.system(weight: .bold))
.foregroundStyle(.gray)
.tint(.green)
.contactAccessButtonCaption(.phone)
.contactAccessButtonStyle(ContactAccessButton.Style(imageWidth: 30))
キーポイント:
.font()は上部テキストとアクションラベルのフォントを制御.foregroundStyle()はメインテキスト色を制御.tint()はアクションラベル色。app テーマ色に追従.contactAccessButtonCaption(.phone)は単一マッチ時に表示する連絡先情報(電話番号など)を指定.contactAccessButtonStyle()は連絡先アバターサイズを制御
プライバシー保護メカニズム
Button 内に表示される連絡先情報は、app が権限を得る前は可視だがプライベート(07:07)。Button は検証済みインタラクションイベントにのみ応答。認可が有効になる前提はボタン内容が明確に読め、遮られていないこと——前景色と背景色のコントラストが低すぎる、またはボタンがクリップ・遮られる場合、タップは権限を付与しない。
CNContactStore で連絡先データを取得
ContactAccessButton のコールバックは識別子配列のみ返す。CNContactStore で完全データを取得(10:11):
func fetchContacts(withIdentifiers identifiers: [String]) async -> [CNContact] {
return await Task {
let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
fetchRequest.predicate = CNContact.predicateForContacts(withIdentifiers: identifiers)
var contacts: [CNContact] = []
do {
try CNContactStore().enumerateContacts(with: fetchRequest) { contact, _ in
contacts.append(contact)
}
} catch {
// ...
}
return contacts
}.value
}
キーポイント:
TaskでラップしてメインスレッドをブロックしないCNContactFetchRequestで取得フィールドを指定。ここでは姓名関連のみCNContact.predicateForContacts(withIdentifiers:)で識別子フィルタenumerateContacts(with:)で結果を配列に収集
contactAccessPicker
一括追加が必要で、個別認可ではない場合は contactAccessPicker を使用(12:47):
@State private var isPresented = false
var body: some View {
Button("Show picker") {
isPresented.toggle()
}.contactAccessPicker(isPresented: $isPresented) { identifiers in
let contacts = await fetchContacts(withIdentifiers: identifiers)
// use the new contacts!
}
}
キーポイント:
.contactAccessPicker()modifier はisPresentedstate にバインド- コールバックは新規追加の連絡先識別子のみ報告。全認可済みではない
- ソーシャル app の「友達を追加」など一括シナリオ向け
CNContactPickerViewControllerとは異なる——後者は一回限りのスナップショット。app の認可範囲は変更しない
4 つの連絡先アクセス方式の選択
| 方式 | 適用シナリオ | 認可レベルの影響 |
|---|---|---|
| CNContactPickerViewController | 一回限りの選択(メール、電話など) | 影響なし。常に利用可能 |
| CNContactStore | 連絡先データ読み書きのメイン入口 | 認可が必要。limited は認可集合のみ |
| ContactAccessButton | 検索フローで個別認可 | limited または notDetermined で使用 |
| contactAccessPicker | 認可範囲の一括管理 | limited のみで使用 |
重要ポイント
-
やること:連絡先検索結果に ContactAccessButton を埋め込み、連絡先全体の一度限り要求に置き換え。価値がある理由:ユーザーが特定連絡先を検索中に認可。文脈が明確で認可率が高い。始め方:検索結果
List下部に条件付きでContactAccessButtonを配置。.limitedまたは.notDetermined状態でのみ表示。 -
やること:既存 app を
.limited認可状態に適応。設定ページに「連絡先アクセスを管理」入口を提供。価値がある理由:iOS 18 ユーザーは limited access を選択可能。app がこの状態を処理しないと検索結果に大量の連絡先が欠落し、体験が劣化。始め方:CNContactStore.authorizationStatus()が.limitedを返すか確認。設定ページにcontactAccessPickerを呼ぶボタンを追加し、ユーザーが認可範囲を拡張可能に。 -
やること:SwiftUI modifier で ContactAccessButton の外観をカスタマイズし、UI に馴染ませる。価値がある理由:デフォルトスタイルは app スタイルと乖離し違和感。コントラスト不足でボタンタップが無効化。始め方:
.font()、.foregroundStyle()、.tint()で app テーマ色に合わせる。.contactAccessButtonCaption(.phone)で単一マッチ時に電話番号などを表示。十分なコントラストを確保。
関連セッション
- Catch up on accessibility in SwiftUI — SwiftUI アクセシビリティ更新。ContactAccessButton の可読性要件はアクセシブル設計と一脈相通
- Demystify SwiftUI containers — SwiftUI コンテナ view の深掘り。ContactAccessButton の List 統合はコンテナメカニズムに依存
- Evolve your document launch experience — ドキュメント起動体験の最適化。「必要時認可」設計思想の別例
- Bring expression to your app with Genmoji — Genmoji 統合。連絡先関連のパーソナライズ表現シナリオ
コメント
GitHub Issues · utterances