Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Overflow reference for the contacts-framework skill. Contains advanced patterns that exceed the main skill file's scope.
@Observableimport Contacts
import SwiftUI
@Observable
@MainActor
final class ContactManager {
let store = CNContactStore()
var contacts: [CNContact] = []
var isAuthorized = false
var authorizationStatus: CNAuthorizationStatus = .notDetermined
func checkAuthorization() {
authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
isAuthorized = authorizationStatus == .authorized
}
func requestAccess() async throws {
let granted = try await store.requestAccess(for: .contacts)
isAuthorized = granted
authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
}
func loadContacts() async throws {
guard isAuthorized else { return }
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor,
CNContactThumbnailImageDataKey as CNKeyDescriptor,
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)
]
contacts = try await Task.detached { [store] in
let request = CNContactFetchRequest(keysToFetch: keys)
request.sortOrder = .givenName
var results: [CNContact] = []
try store.enumerateContacts(with: request) { contact, _ in
results.append(contact)
}
return results
}.value
}
func formattedName(for contact: CNContact) -> String {
CNContactFormatter.string(from: contact, style: .fullName)
?? "\(contact.givenName) \(contact.familyName)"
}
}struct ContactListView: View {
@Environment(ContactManager.self) private var manager
var body: some View {
NavigationStack {
Group {
if !manager.isAuthorized {
ContentUnavailableView {
Label("Contacts Access", systemImage: "person.crop.circle.badge.questionmark")
} description: {
Text("Grant access to view your contacts.")
} actions: {
Button("Allow Access") {
Task { try? await manager.requestAccess() }
}
.buttonStyle(.borderedProminent)
}
} else {
contactList
}
}
.navigationTitle("Contacts")
.task {
manager.checkAuthorization()
if manager.isAuthorized {
try? await manager.loadContacts()
}
}
}
}
private var contactList: some View {
List(manager.contacts, id: \.identifier) { contact in
HStack {
contactAvatar(contact)
VStack(alignment: .leading) {
Text(manager.formattedName(for: contact))
.font(.body)
if let phone = contact.phoneNumbers.first?.value.stringValue {
Text(phone)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
}
@ViewBuilder
private func contactAvatar(_ contact: CNContact) -> some View {
if let imageData = contact.thumbnailImageData,
let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
.resizable()
.scaledToFill()
.frame(width: 40, height: 40)
.clipShape(Circle())
} else {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 40, height: 40)
.foregroundStyle(.secondary)
}
}
}import SwiftUI
import ContactsUI
struct MultiContactPicker: UIViewControllerRepresentable {
@Binding var selectedContacts: [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: MultiContactPicker
init(_ parent: MultiContactPicker) {
self.parent = parent
}
func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
parent.selectedContacts = contacts
}
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {}
}
}Configure the picker to only return email addresses.
struct EmailPicker: UIViewControllerRepresentable {
@Binding var selectedEmail: String?
func makeUIViewController(context: Context) -> CNContactPickerViewController {
let picker = CNContactPickerViewController()
picker.delegate = context.coordinator
// Only show contacts with emails
picker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")
// Show contact detail so user can pick a specific email
picker.predicateForSelectionOfProperty = NSPredicate(
format: "key == 'emailAddresses'"
)
picker.displayedPropertyKeys = [CNContactEmailAddressesKey]
return picker
}
func updateUIViewController(_ uiViewController: CNContactPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, CNContactPickerDelegate {
let parent: EmailPicker
init(_ parent: EmailPicker) {
self.parent = parent
}
func contactPicker(
_ picker: CNContactPickerViewController,
didSelect contactProperty: CNContactProperty
) {
parent.selectedEmail = contactProperty.value as? String
}
}
}// By name
let namePredicate = CNContact.predicateForContacts(matchingName: "John")
// By email address
let emailPredicate = CNContact.predicateForContacts(matchingEmailAddress: "john@example.com")
// By phone number
let phonePredicate = CNContact.predicateForContacts(
matching: CNPhoneNumber(stringValue: "+1234567890")
)
// By identifiers (batch fetch)
let idsPredicate = CNContact.predicateForContacts(withIdentifiers: ["id1", "id2", "id3"])
// By group
let groupPredicate = CNContact.predicateForContactsInGroup(withIdentifier: groupId)
// By container
let containerPredicate = CNContact.predicateForContactsInContainer(
withIdentifier: containerId
)For complex filtering not supported by predicates, enumerate and filter in memory.
func fetchContactsWithBirthday(in month: Int) throws -> [CNContact] {
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactBirthdayKey as CNKeyDescriptor
]
let request = CNContactFetchRequest(keysToFetch: keys)
var contacts: [CNContact] = []
try store.enumerateContacts(with: request) { contact, _ in
if let birthday = contact.birthday, birthday.month == month {
contacts.append(contact)
}
}
return contacts
}func exportToVCard(contacts: [CNContact]) throws -> Data {
return try CNContactVCardSerialization.data(with: contacts)
}
// Save to file
func saveVCard(contacts: [CNContact], to url: URL) throws {
let data = try CNContactVCardSerialization.data(with: contacts)
try data.write(to: url)
}func importFromVCard(data: Data) throws -> [CNContact] {
return try CNContactVCardSerialization.contacts(with: data)
}
// Save imported contacts to the store
func importAndSave(data: Data) throws {
let contacts = try CNContactVCardSerialization.contacts(with: data)
let saveRequest = CNSaveRequest()
for contact in contacts {
guard let mutable = contact.mutableCopy() as? CNMutableContact else { continue }
saveRequest.add(mutable, toContainerWithIdentifier: nil)
}
try store.execute(saveRequest)
}func fetchGroups() throws -> [CNGroup] {
return try store.groups(matching: nil) // nil returns all groups
}
func fetchContactsInGroup(_ group: CNGroup) throws -> [CNContact] {
let predicate = CNContact.predicateForContactsInGroup(withIdentifier: group.identifier)
let keys: [CNKeyDescriptor] = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor
]
return try store.unifiedContacts(matching: predicate, keysToFetch: keys)
}func createGroup(name: String) throws {
let group = CNMutableGroup()
group.name = name
let saveRequest = CNSaveRequest()
saveRequest.add(group, toContainerWithIdentifier: nil)
try store.execute(saveRequest)
}
func addContactToGroup(contact: CNContact, group: CNGroup) throws {
let saveRequest = CNSaveRequest()
saveRequest.addMember(contact, to: group)
try store.execute(saveRequest)
}
func removeContactFromGroup(contact: CNContact, group: CNGroup) throws {
let saveRequest = CNSaveRequest()
saveRequest.removeMember(contact, from: group)
try store.execute(saveRequest)
}Use CNChangeHistoryFetchRequest to fetch incremental changes since a saved token.
This is efficient for syncing contact data.
func fetchChanges(since token: Data?) throws {
let request = CNChangeHistoryFetchRequest()
request.startingToken = token
request.additionalContactKeyDescriptors = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor
]
let result = try store.enumerateChanges(matching: request)
for event in result {
switch event {
case let addEvent as CNChangeHistoryAddContactEvent:
let contact = addEvent.contact
print("Added: \(contact.givenName) \(contact.familyName)")
case let updateEvent as CNChangeHistoryUpdateContactEvent:
let contact = updateEvent.contact
print("Updated: \(contact.givenName) \(contact.familyName)")
case let deleteEvent as CNChangeHistoryDeleteContactEvent:
let identifier = deleteEvent.contactIdentifier
print("Deleted: \(identifier)")
default:
break
}
}
// Save the new token for next sync
let newToken = store.currentHistoryToken
}func observeChanges(handler: @escaping () -> Void) -> NSObjectProtocol {
NotificationCenter.default.addObserver(
forName: .CNContactStoreDidChange,
object: nil,
queue: .main
) { _ in
handler()
}
}skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references