Mac菜单栏应用无法获取通讯录访问权限求助
Mac菜单栏应用通讯录权限问题排查与解决
问题现象
开发Mac菜单栏应用时,已在info.plist中配置Privacy - Contacts Usage Description,但调用通讯录接口时始终报错:
Error Domain=CNErrorDomain Code=100 "Access Denied" UserInfo={NSLocalizedDescription=Access Denied, NSLocalizedFailureReason=This application has not been granted permission to access Contacts.}
同时系统设置「隐私与安全性-通讯录」中看不到该应用的权限请求记录。
问题原因与解决方案
Mac菜单栏应用(MenuBarExtra)属于后台应用范畴,默认不会触发通讯录权限弹窗,也不会在隐私设置中显示,需额外配置权限相关项:
1. 补充后台权限配置(关键)
在info.plist中添加NSBackgroundModes数组,并包含contacts模式,让系统识别应用需要通讯录权限:
<key>NSBackgroundModes</key> <array> <string>contacts</string> </array>
此配置会触发系统权限请求弹窗,同时让应用出现在隐私设置的通讯录列表中。
2. 修正权限请求后的逻辑
原代码中请求权限后未重新执行通讯录获取操作,需修改fetchContacts方法:
@MainActor func fetchContacts() async { let store = CNContactStore() switch CNContactStore.authorizationStatus(for: .contacts) { case .notDetermined: do { let granted = try await store.requestAccess(for: .contacts) if granted { // 权限允许后递归调用,自动进入授权分支 await fetchContacts() } else { print("用户拒绝了通讯录权限") } } catch { print("请求权限失败:", error) } case .authorized: contacts.removeAll() let request = CNContactFetchRequest(keysToFetch: [ CNContactGivenNameKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor, CNContactThumbnailImageDataKey as CNKeyDescriptor ]) do { try store.enumerateContacts(with: request, usingBlock: { contact, stop in contacts.append(Contact(firstName: contact.givenName, phoneNumbers: contact.phoneNumbers, avatar: contact.thumbnailImageData)) }) } catch { print("枚举通讯录失败:", error) } default: print("无通讯录访问权限") } }
修改点:请求权限成功后自动重新调用方法获取联系人,移除冗余的权限重复检查。
3. 验证步骤
- 清理Xcode缓存(Cmd+Shift+K),重新编译运行
- 点击「Get contacts」按钮,系统会弹出通讯录权限请求弹窗
- 授权后,系统设置「隐私与安全性-通讯录」中会显示该应用
完整代码参考
ContactStore.swift
import Contacts import SwiftUI struct Contact { let id = UUID().uuidString let firstName: String let phoneNumbers: [CNLabeledValue<CNPhoneNumber>] let avatar: Data? } class ContactStore: ObservableObject { @Published var contacts = [Contact]() @MainActor func fetchContacts() async { let store = CNContactStore() switch CNContactStore.authorizationStatus(for: .contacts) { case .notDetermined: do { let granted = try await store.requestAccess(for: .contacts) if granted { await fetchContacts() } else { print("用户拒绝通讯录权限") } } catch { print("请求权限错误:", error) } case .authorized: contacts.removeAll() let request = CNContactFetchRequest(keysToFetch: [ CNContactGivenNameKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor, CNContactThumbnailImageDataKey as CNKeyDescriptor ]) do { try store.enumerateContacts(with: request, usingBlock: { contact, stop in contacts.append(Contact(firstName: contact.givenName, phoneNumbers: contact.phoneNumbers, avatar: contact.thumbnailImageData)) }) } catch { print("枚举通讯录错误:", error) } default: print("无法访问通讯录数据") } } }
TestContactAppApp.swift
import SwiftUI @main struct TestContactAppApp: App { @StateObject var contactsStore = ContactStore() var body: some Scene { MenuBarExtra { ContentView() .environmentObject(contactsStore) } label : { Image(systemName: "pencil.circle.fill") } .menuBarExtraStyle(.window) } }
ContentView.swift
import SwiftUI struct ContentView: View { @EnvironmentObject var contactStore: ContactStore var body: some View { VStack { Button("获取通讯录") { Task { await contactStore.fetchContacts() } } } .padding() .frame(width: 100, height: 100) } }
内容的提问来源于stack exchange,提问作者Rahul Bir
相关产品推荐
相关产品推荐

