WWDC Quick Look 💓 By SwiftGGTeam
Meet the Contact Access Button

Meet the Contact Access Button

元の動画を見る

ハイライト

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 は isPresented state にバインド
  • コールバックは新規追加の連絡先識別子のみ報告。全認可済みではない
  • ソーシャル 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) で単一マッチ時に電話番号などを表示。十分なコントラストを確保。

関連セッション

コメント

GitHub Issues · utterances