CtrlK
BlogDocsLog inGet started
Tessl Logo

thiennc-tesoglobal/ios-skills

Community-maintained Agent Skills for complete Swift and Apple-platform app delivery.

72

Quality

90%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Medium

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

SKILL.mdskills/contacts-framework/

name:
contacts-framework
description:
Read, create, update, and pick contacts using the Contacts and ContactsUI frameworks. Use when fetching contact data, saving new contacts, wrapping CNContactPickerViewController in SwiftUI, handling contact permissions, or working with CNContactStore fetch and save requests.

Contacts Framework

Use CNContactStore, CNSaveRequest, and ContactsUI to fetch, mutate, or let the user select contacts. Prefer the system picker when full address-book access is unnecessary.

Contents

  • Choose the access model
  • Setup and authorization
  • Fetch invariants
  • Mutation invariants
  • Concurrency and cache invalidation
  • Common mistakes
  • Review checklist
  • References

Choose the access model

NeedAPI
User chooses one or more contacts without broad permissionCNContactPickerViewController
App reads or writes its authorized contact setCNContactStore
User expands an iOS 18+ limited setContactAccessButton or contactAccessPicker
Import/export or sharingCNContactVCardSerialization

Read Contacts extended patterns for a complete observable manager, SwiftUI lists, single/multi-select picker wrappers, email-only selection, optimized search, vCard import/export, groups, and change notification handling.

Setup and authorization

  • Add NSContactsUsageDescription before direct Contacts API access; missing it causes termination.
  • Ordinary access needs no entitlement. Reading or writing CNContact.note requires the Apple-approved com.apple.developer.contacts.notes entitlement.
  • The system contact picker does not require broad Contacts authorization; the app receives only selected data.

Treat authorization states explicitly:

StatusBehavior
.notDeterminedRequest only from a user-understood action
.authorizedFull access
.limitedUsable, but only for granted or app-created contacts
.deniedExplain the feature and route to Settings when appropriate
.restrictedDisable the operation; do not repeatedly prompt

Fetch invariants

Only fetch keys the caller will access. Reading an unfetched property raises CNContactPropertyNotFetchedException.

@preconcurrency import Contacts

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

let request = CNContactFetchRequest(keysToFetch: keys)
try store.enumerateContacts(with: request) { contact, stop in
    consume(contact)
}

Use unifiedContacts(matching:keysToFetch:) for predicate queries, unifiedContact(withIdentifier:keysToFetch:) for known identifiers, and enumeration for the authorized address book. Avoid full-resolution image data unless the UI truly requires it. For large caches, fetch identifiers first and hydrate details in bounded batches.

Mutation invariants

  • Create with CNMutableContact and CNSaveRequest.add.
  • Update or delete by fetching the required properties, creating mutableCopy(), then adding the operation to a fresh save request.
  • store.execute(request) returning without throwing is the success boundary. Advance app state or clear drafts only afterward.
  • On failure, preserve the user's intent, surface the error, correct known authorization/container/input causes, refetch stale contacts when possible, and construct a new request. Do not blindly replay a destructive request.
  • Serialize overlapping saves and never mutate a request while execute uses it.
let mutable = CNMutableContact()
mutable.givenName = "Taylor"

let request = CNSaveRequest()
request.add(mutable, toContainerWithIdentifier: nil)
try store.execute(request)

Concurrency and cache invalidation

Enumeration is I/O-heavy; keep it off the main actor. With strict concurrency, use @preconcurrency import Contacts only at the framework boundary or map CNContact values into app-owned Sendable models before crossing actors.

Observe .CNContactStoreDidChange, invalidate cached CNContact objects, and refetch the authorized set. Reuse one store instead of constructing stores per row or query.

Common Mistakes

  • Requesting full access when a picker satisfies the feature.
  • Treating .limited as denial or assuming it exposes the full address book.
  • Fetching every key, especially full image data.
  • Accessing a property not included in keysToFetch.
  • Attempting to mutate immutable CNContact directly.
  • Updating UI/cache before execute succeeds.
  • Enumerating contacts on the main actor or retaining stale contact objects.

Review Checklist

  • Usage description and note entitlement requirements are correct.
  • Picker is preferred when broad access is unnecessary.
  • Every authorization state, including .limited, has product behavior.
  • Fetch descriptors include exactly the accessed properties.
  • Name formatting uses the formatter's required-key descriptor.
  • Create/update/delete use fresh CNSaveRequest values and mutable contacts.
  • App state changes only after a successful save; failures preserve intent.
  • Heavy reads run off the main actor and cross actors safely.
  • Store-change notification invalidates and refetches caches.
  • One long-lived CNContactStore is reused.

References

skills

contacts-framework

.mcp.json

README.md

tile.json