Token导航 LogoToken导航TokenDH.com
待分类需要联网github未标认证来源可访问许可证需确认审计通过

contacts-framework联系框架

Agent Skill

contacts-framework 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

25,872

周安装

1,127

GitHub Stars

474

下载量

9,064
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:contacts-framework(联系框架)
来源仓库:https://github.com/dpearson2699/swift-ios-skills
仓库路径:skills/contacts-framework
安装命令:
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill contacts-framework
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill contacts-framework

简介

Contacts Framework 提供 iOS Swift 环境下 CNContactStore 集成方案。

  • 涵盖授权申请、联系人拉取、创建更新、选择器调用与变更监听等完整生命周期。
  • 目标平台为 Swift 6.3 / iOS 26+,需配置 NSContactsUsageDescription 权限声明。
  • 涉及用户通讯录访问,必须遵循最小权限原则并处理授权拒绝情况。
  • contacts-framework 属于待分类类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

Contacts Framework

Fetch, create, update, and pick contacts from the user's Contacts database using CNContactStore, CNSaveRequest, and CNContactPickerViewController. Targets Swift 6.3 / iOS 26+.

Contents

Setup

Project Configuration

  1. Add NSContactsUsageDescription to Info.plist explaining why the app accesses contacts
  2. No additional capability or entitlement is required for basic Contacts access
  3. For contact notes access, add the com.apple.developer.contacts.notes entitlement

Imports

import Contacts       // CNContactStore, CNSaveRequest, CNContact
import ContactsUI     // CNContactPickerViewController

Authorization

Request access before fetching or saving contacts. The picker (CNContactPickerViewController) does not require authorization -- the system grants access only to the contacts the user selects.

let store = CNContactStore()

func requestAccess() async throws -> Bool {
    return try await store.requestAccess(for: .contacts)
}

// Check current status without prompting
func checkStatus() -> CNAuthorizationStatus {
    CNContactStore.authorizationStatus(for: .contacts)
}

Authorization States

StatusMeaning
.notDeterminedUser has not been prompted yet
.authorizedFull read/write access granted
.deniedUser denied access; direct to Settings
.restrictedParental controls or MDM restrict access
.limitediOS 18+: user granted access to selected contacts only

Fetching Contacts

Use unifiedContacts(matching:keysToFetch:) for predicate-based queries. Use enumerateContacts(with:usingBlock:) for batch enumeration of all contacts.

Fetch by Name

func fetchContacts(named name: String) throws -> [CNContact] {
    let predicate = CNContact.predicateForContacts(matchingName: name)
    let keys: [CNKeyDescriptor] = [
        CNContactGivenNameKey as CNKeyDescriptor,
        CNContactFamilyNameKey as CNKeyDescriptor,
        CNContactPhoneNumbersKey as CNKeyDescriptor
    ]
    return try store.unifiedContacts(matching: predicate, keysToFetch: keys)
}

Fetch by Identifier

func fetchContact(identifier: String) throws -> CNContact {
    let keys: [CNKeyDescriptor] = [
        CNContactGivenNameKey as CNKeyDescriptor,
        CNContactFamilyNameKey as CNKeyDescriptor,
        CNContactEmailAddressesKey as CNKeyDescriptor
    ]
    return try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
}

Enumerate All Contacts

Perform I/O-heavy enumeration off the main thread.

func fetchAllContacts() throws -> [CNContact] {
    let keys: [CNKeyDescriptor] = [
        CNContactGivenNameKey as CNKeyDescriptor,
        CNContactFamilyNameKey as CNKeyDescriptor
    ]
    let request = CNContactFetchRequest(keysToFetch: keys)
    request.sortOrder = .givenName

    var contacts: [CNContact] = []
    try store.enumerateContacts(with: request) { contact, _ in
        contacts.append(contact)
    }
    return contacts
}

Key Descriptors

Only fetch the properties you need. Accessing an unfetched property throws CNContactPropertyNotFetchedException.

Common Keys

KeyProperty
CNContactGivenNameKeyFirst name
CNContactFamilyNameKeyLast name
CNContactPhoneNumbersKeyPhone numbers array
CNContactEmailAddressesKeyEmail addresses array
CNContactPostalAddressesKeyMailing addresses array
CNContactImageDataKeyFull-resolution contact photo
CNContactThumbnailImageDataKeyThumbnail contact photo
CNContactBirthdayKeyBirthday date components
CNContactOrganizationNameKeyCompany name

Composite Key Descriptors

Use CNContactFormatter.descriptorForRequiredKeys(for:) to fetch all keys needed for formatting a contact's name.

let nameKeys = CNContactFormatter.descriptorForRequiredKeys(for: .fullName)
let keys: [CNKeyDescriptor] = [nameKeys, CNContactPhoneNumbersKey as CNKeyDescriptor]

Creating and Updating Contacts

Use CNMutableContact to build new contacts and CNSaveRequest to persist changes.

Creating a New Contact

func createContact(givenName: String, familyName: String, phone: String) throws {
    let contact = CNMutableContact()
    contact.givenName = givenName
    contact.familyName = familyName
    contact.phoneNumbers = [
        CNLabeledValue(
            label: CNLabelPhoneNumberMobile,
            value: CNPhoneNumber(stringValue: phone)
        )
    ]

    let saveRequest = CNSaveRequest()
    saveRequest.add(contact, toContainerWithIdentifier: nil) // nil = default container
    try store.execute(saveRequest)
}

Updating an Existing Contact

You must fetch the contact with the properties you intend to modify, create a mutable copy, change the properties, then save.

func updateContactEmail(identifier: String, email: String) throws {
    let keys: [CNKeyDescriptor] = [
        CNContactEmailAddressesKey as CNKeyDescriptor
    ]
    let contact = try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
    guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }

    mutable.emailAddresses.append(
        CNLabeledValue(label: CNLabelWork, value: email as NSString)
    )

    let saveRequest = CNSaveRequest()
    saveRequest.update(mutable)
    try store.execute(saveRequest)
}

Deleting a Contact

func deleteContact(identifier: String) throws {
    let keys: [CNKeyDescriptor] = [CNContactIdentifierKey as CNKeyDescriptor]
    let contact = try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
    guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }

    let saveRequest = CNSaveRequest()
    saveRequest.delete(mutable)
    try store.execute(saveRequest)
}

Contact Picker

CNContactPickerViewController lets users pick contacts without granting full Contacts access. The app receives only the selected contact data.

SwiftUI Wrapper

import SwiftUI
import ContactsUI

struct ContactPicker: UIViewControllerRepresentable {
    @Binding var selectedContact: CNContact?

    func makeUIViewController(context: Context) -> CNContactPickerViewController {
        let picker = CNContactPickerViewController()
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: CNContactPickerViewController, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    final class Coordinator: NSObject, CNContactPickerDelegate {
        let parent: ContactPicker

        init(_ parent: ContactPicker) {
            self.parent = parent
        }

        func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
            parent.selectedContact = contact
        }

        func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
            parent.selectedContact = nil
        }
    }
}

Using the Picker

struct ContactSelectionView: View {
    @State private var selectedContact: CNContact?
    @State private var showPicker = false

    var body: some View {
        VStack {
            if let contact = selectedContact {
                Text("\(contact.givenName) \(contact.familyName)")
            }
            Button("Select Contact") {
                showPicker = true
            }
        }
        .sheet(isPresented: $showPicker) {
            ContactPicker(selectedContact: $selectedContact)
        }
    }
}

Filtering the Picker

Use predicates to control which contacts appear and what the user can select.

let picker = CNContactPickerViewController()
// Only show contacts that have an email address
picker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")
// Selecting a contact returns it directly (no detail card)
picker.predicateForSelectionOfContact = NSPredicate(value: true)

Observing Changes

Listen for external contact database changes to refresh cached data.

func observeContactChanges() {
    NotificationCenter.default.addObserver(
        forName: .CNContactStoreDidChange,
        object: nil,
        queue: .main
    ) { _ in
        // Refetch contacts -- cached CNContact objects are stale
        refreshContacts()
    }
}

Common Mistakes

DON'T: Fetch all keys when you only need a name

Over-fetching wastes memory and slows queries, especially for contacts with large photos.

// WRONG: Fetches everything including full-resolution photos
let keys: [CNKeyDescriptor] = [CNContactCompleteNameKey as CNKeyDescriptor,
    CNContactImageDataKey as CNKeyDescriptor,
    CNContactPhoneNumbersKey as CNKeyDescriptor,
    CNContactEmailAddressesKey as CNKeyDescriptor,
    CNContactPostalAddressesKey as CNKeyDescriptor,
    CNContactBirthdayKey as CNKeyDescriptor]

// CORRECT: Fetch only what you display
let keys: [CNKeyDescriptor] = [
    CNContactGivenNameKey as CNKeyDescriptor,
    CNContactFamilyNameKey as CNKeyDescriptor
]

DON'T: Access unfetched properties

Accessing a property that was not in keysToFetch throws CNContactPropertyNotFetchedException at runtime.

// WRONG: Only fetched name keys, now accessing phone
let keys: [CNKeyDescriptor] = [CNContactGivenNameKey as CNKeyDescriptor]
let contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)
let phone = contact.phoneNumbers.first // CRASH

// CORRECT: Include the key you need
let keys: [CNKeyDescriptor] = [
    CNContactGivenNameKey as CNKeyDescriptor,
    CNContactPhoneNumbersKey as CNKeyDescriptor
]

DON'T: Mutate a CNContact directly

CNContact is immutable. You must call mutableCopy() to get a CNMutableContact.

// WRONG: CNContact has no setter
let contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)
contact.givenName = "New Name" // Compile error

// CORRECT: Create mutable copy
guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }
mutable.givenName = "New Name"

DON'T: Skip authorization and assume access

Without calling requestAccess(for:), fetch methods return empty results or throw.

// WRONG: Jump straight to fetch
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)

// CORRECT: Check or request access first
let granted = try await store.requestAccess(for: .contacts)
guard granted else { return }
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)

DON'T: Run heavy fetches on the main thread

enumerateContacts performs I/O. Running it on the main thread blocks the UI.

// WRONG: Main thread enumeration
func loadContacts() {
    try store.enumerateContacts(with: request) { contact, _ in ... }
}

// CORRECT: Run on a background thread
func loadContacts() async throws -> [CNContact] {
    try await Task.detached {
        var results: [CNContact] = []
        try store.enumerateContacts(with: request) { contact, _ in
            results.append(contact)
        }
        return results
    }.value
}

Review Checklist

  • NSContactsUsageDescription added to Info.plist
  • requestAccess(for:.contacts) called before fetch or save operations
  • Authorization denial handled gracefully (guide user to Settings)
  • Only needed CNKeyDescriptor keys included in fetch requests
  • CNContactFormatter.descriptorForRequiredKeys(for:) used when formatting names
  • Mutable copy created via mutableCopy() before modifying contacts
  • CNSaveRequest used for all create/update/delete operations
  • Heavy fetches (enumerateContacts) run off the main thread
  • CNContactStoreDidChange observed to refresh cached contacts
  • CNContactPickerViewController used when full Contacts access is unnecessary
  • Picker predicates set before presenting the picker view controller
  • Single CNContactStore instance reused across the app

References

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

35.42%
按下载量换算3,210

Claude

31.57%
按下载量换算2,862

Cursor

18.97%
按下载量换算1,719

Gemini CLI

10.07%
按下载量换算913

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills