Highlight
iOS 18 对联系人授权模型做了一个根本性的改动:引入了 Limited Access(受限访问)。用户授权时不再只有”允许”和”拒绝”两个选项,而是可以选择只分享部分联系人。授权弹窗变成了两步——第一步问是否允许访问,第二步让用户选择具体哪些联系人可以分享,或者全部开放。
核心内容
过去,app 请求通讯录权限时,用户面对的是一个”全有或全无”的选择——要么把整个通讯录交出去,要么什么都不给。这对用户来说是个艰难的决定。一个聊天 app 可能只需要联系三五个人,但为了这点需求不得不开放全部联系人。很多用户因为这个选择而直接点了”拒绝”。
iOS 18 改变了这个局面。联系人授权现在支持 Limited Access(受限访问):第一步弹窗询问是否分享联系人,第二步让用户选择具体哪些联系人可以分享,或者选择全部开放。确认页面会展示用户选中的联系人,并提示这些选择以后可以修改。授权级别从原来的三种(notDetermined / authorized / denied)扩展为四种,新增了 .limited。处于 limited 状态时,app 只能读取用户授权的那些联系人,但仍然可以修改或创建联系人。
对应这个新模型,苹果推出了两个 API。ContactAccessPicker 是一个全屏模态选择器,适合批量添加联系人的场景(比如社交 app 的好友匹配功能)。而 ContactAccessButton 更巧妙——它是一个 SwiftUI 组件,嵌入你的搜索结果列表中,展示 app 还没有权限访问的匹配联系人,用户一键即可授权。它甚至可以在 app 还处于 notDetermined 状态时使用,点击后自动弹出简化的授权请求。因为请求发生在用户明确搜索某个联系人的那一刻,用户很清楚 app 为什么要这个权限,授权通过率自然更高。
详细内容
四种授权级别
iOS 18 联系人授权有四个状态(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 和两个专用 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状态- 回调只报告新增的联系人标识符,不是全部已授权的
- 适合社交 app 中”添加更多好友”这类批量场景
- 和
CNContactPickerViewController不同——后者是一次性快照,不改变 app 的授权范围
四种联系人访问方式的选择
| 方式 | 适用场景 | 是否受授权级别影响 |
|---|---|---|
| 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)在单匹配时展示电话号码等具体信息,确保对比度足够。
关联 Session
- Catch up on accessibility in SwiftUI — SwiftUI 无障碍更新,ContactAccessButton 的可读性要求与无障碍设计一脉相承
- Demystify SwiftUI containers — 深入理解 SwiftUI 容器视图,ContactAccessButton 的 List 集成依赖容器机制
- Evolve your document launch experience — 文档启动体验优化,同样是”按需授权”设计思路的体现
- Bring expression to your app with Genmoji — Genmoji 集成,联系人相关的个性化表达场景
评论
GitHub Issues · utterances