{"id":"6ccb1f7a-f5d0-4788-bb95-30ff26794ec6","shortId":"Rn3kTK","kind":"skill","title":"Contacts Framework","tagline":"Swift Ios Skills skill by Dpearson2699","description":"# Contacts Framework\n\nFetch, create, update, and pick contacts from the user's Contacts database using\n`CNContactStore`, `CNSaveRequest`, and `CNContactPickerViewController`. Targets\nSwift 6.3 / iOS 26+.\n\n## Contents\n\n- [Setup](#setup)\n- [Authorization](#authorization)\n- [Fetching Contacts](#fetching-contacts)\n- [Key Descriptors](#key-descriptors)\n- [Creating and Updating Contacts](#creating-and-updating-contacts)\n- [Contact Picker](#contact-picker)\n- [Observing Changes](#observing-changes)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n## Setup\n\n### Project Configuration\n\n1. Add `NSContactsUsageDescription` to Info.plist explaining why the app accesses contacts\n2. No additional capability or entitlement is required for basic Contacts access\n3. For contact notes access, add the `com.apple.developer.contacts.notes` entitlement\n\n### Imports\n\n```swift\nimport Contacts       // CNContactStore, CNSaveRequest, CNContact\nimport ContactsUI     // CNContactPickerViewController\n```\n\n## Authorization\n\nRequest access before fetching or saving contacts. The picker (`CNContactPickerViewController`)\ndoes not require authorization -- the system grants access only to the contacts\nthe user selects.\n\n```swift\nlet store = CNContactStore()\n\nfunc requestAccess() async throws -> Bool {\n    return try await store.requestAccess(for: .contacts)\n}\n\n// Check current status without prompting\nfunc checkStatus() -> CNAuthorizationStatus {\n    CNContactStore.authorizationStatus(for: .contacts)\n}\n```\n\n### Authorization States\n\n| Status | Meaning |\n|---|---|\n| `.notDetermined` | User has not been prompted yet |\n| `.authorized` | Full read/write access granted |\n| `.denied` | User denied access; direct to Settings |\n| `.restricted` | Parental controls or MDM restrict access |\n| `.limited` | iOS 18+: user granted access to selected contacts only |\n\n## Fetching Contacts\n\nUse `unifiedContacts(matching:keysToFetch:)` for predicate-based queries.\nUse `enumerateContacts(with:usingBlock:)` for batch enumeration of all contacts.\n\n### Fetch by Name\n\n```swift\nfunc fetchContacts(named name: String) throws -> [CNContact] {\n    let predicate = CNContact.predicateForContacts(matchingName: name)\n    let keys: [CNKeyDescriptor] = [\n        CNContactGivenNameKey as CNKeyDescriptor,\n        CNContactFamilyNameKey as CNKeyDescriptor,\n        CNContactPhoneNumbersKey as CNKeyDescriptor\n    ]\n    return try store.unifiedContacts(matching: predicate, keysToFetch: keys)\n}\n```\n\n### Fetch by Identifier\n\n```swift\nfunc fetchContact(identifier: String) throws -> CNContact {\n    let keys: [CNKeyDescriptor] = [\n        CNContactGivenNameKey as CNKeyDescriptor,\n        CNContactFamilyNameKey as CNKeyDescriptor,\n        CNContactEmailAddressesKey as CNKeyDescriptor\n    ]\n    return try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)\n}\n```\n\n### Enumerate All Contacts\n\nPerform I/O-heavy enumeration off the main thread.\n\n```swift\nfunc fetchAllContacts() throws -> [CNContact] {\n    let keys: [CNKeyDescriptor] = [\n        CNContactGivenNameKey as CNKeyDescriptor,\n        CNContactFamilyNameKey as CNKeyDescriptor\n    ]\n    let request = CNContactFetchRequest(keysToFetch: keys)\n    request.sortOrder = .givenName\n\n    var contacts: [CNContact] = []\n    try store.enumerateContacts(with: request) { contact, _ in\n        contacts.append(contact)\n    }\n    return contacts\n}\n```\n\n## Key Descriptors\n\nOnly fetch the properties you need. Accessing an unfetched property throws\n`CNContactPropertyNotFetchedException`.\n\n### Common Keys\n\n| Key | Property |\n|---|---|\n| `CNContactGivenNameKey` | First name |\n| `CNContactFamilyNameKey` | Last name |\n| `CNContactPhoneNumbersKey` | Phone numbers array |\n| `CNContactEmailAddressesKey` | Email addresses array |\n| `CNContactPostalAddressesKey` | Mailing addresses array |\n| `CNContactImageDataKey` | Full-resolution contact photo |\n| `CNContactThumbnailImageDataKey` | Thumbnail contact photo |\n| `CNContactBirthdayKey` | Birthday date components |\n| `CNContactOrganizationNameKey` | Company name |\n\n### Composite Key Descriptors\n\nUse `CNContactFormatter.descriptorForRequiredKeys(for:)` to fetch all keys needed\nfor formatting a contact's name.\n\n```swift\nlet nameKeys = CNContactFormatter.descriptorForRequiredKeys(for: .fullName)\nlet keys: [CNKeyDescriptor] = [nameKeys, CNContactPhoneNumbersKey as CNKeyDescriptor]\n```\n\n## Creating and Updating Contacts\n\nUse `CNMutableContact` to build new contacts and `CNSaveRequest` to persist changes.\n\n### Creating a New Contact\n\n```swift\nfunc createContact(givenName: String, familyName: String, phone: String) throws {\n    let contact = CNMutableContact()\n    contact.givenName = givenName\n    contact.familyName = familyName\n    contact.phoneNumbers = [\n        CNLabeledValue(\n            label: CNLabelPhoneNumberMobile,\n            value: CNPhoneNumber(stringValue: phone)\n        )\n    ]\n\n    let saveRequest = CNSaveRequest()\n    saveRequest.add(contact, toContainerWithIdentifier: nil) // nil = default container\n    try store.execute(saveRequest)\n}\n```\n\n### Updating an Existing Contact\n\nYou must fetch the contact with the properties you intend to modify, create a\nmutable copy, change the properties, then save.\n\n```swift\nfunc updateContactEmail(identifier: String, email: String) throws {\n    let keys: [CNKeyDescriptor] = [\n        CNContactEmailAddressesKey as CNKeyDescriptor\n    ]\n    let contact = try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)\n    guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }\n\n    mutable.emailAddresses.append(\n        CNLabeledValue(label: CNLabelWork, value: email as NSString)\n    )\n\n    let saveRequest = CNSaveRequest()\n    saveRequest.update(mutable)\n    try store.execute(saveRequest)\n}\n```\n\n### Deleting a Contact\n\n```swift\nfunc deleteContact(identifier: String) throws {\n    let keys: [CNKeyDescriptor] = [CNContactIdentifierKey as CNKeyDescriptor]\n    let contact = try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)\n    guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }\n\n    let saveRequest = CNSaveRequest()\n    saveRequest.delete(mutable)\n    try store.execute(saveRequest)\n}\n```\n\n## Contact Picker\n\n`CNContactPickerViewController` lets users pick contacts without granting full\nContacts access. The app receives only the selected contact data.\n\n### SwiftUI Wrapper\n\n```swift\nimport SwiftUI\nimport ContactsUI\n\nstruct ContactPicker: UIViewControllerRepresentable {\n    @Binding var selectedContact: CNContact?\n\n    func makeUIViewController(context: Context) -> CNContactPickerViewController {\n        let picker = CNContactPickerViewController()\n        picker.delegate = context.coordinator\n        return picker\n    }\n\n    func updateUIViewController(_ uiViewController: CNContactPickerViewController, context: Context) {}\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(self)\n    }\n\n    final class Coordinator: NSObject, CNContactPickerDelegate {\n        let parent: ContactPicker\n\n        init(_ parent: ContactPicker) {\n            self.parent = parent\n        }\n\n        func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {\n            parent.selectedContact = contact\n        }\n\n        func contactPickerDidCancel(_ picker: CNContactPickerViewController) {\n            parent.selectedContact = nil\n        }\n    }\n}\n```\n\n### Using the Picker\n\n```swift\nstruct ContactSelectionView: View {\n    @State private var selectedContact: CNContact?\n    @State private var showPicker = false\n\n    var body: some View {\n        VStack {\n            if let contact = selectedContact {\n                Text(\"\\(contact.givenName) \\(contact.familyName)\")\n            }\n            Button(\"Select Contact\") {\n                showPicker = true\n            }\n        }\n        .sheet(isPresented: $showPicker) {\n            ContactPicker(selectedContact: $selectedContact)\n        }\n    }\n}\n```\n\n### Filtering the Picker\n\nUse predicates to control which contacts appear and what the user can select.\n\n```swift\nlet picker = CNContactPickerViewController()\n// Only show contacts that have an email address\npicker.predicateForEnablingContact = NSPredicate(format: \"emailAddresses.@count > 0\")\n// Selecting a contact returns it directly (no detail card)\npicker.predicateForSelectionOfContact = NSPredicate(value: true)\n```\n\n## Observing Changes\n\nListen for external contact database changes to refresh cached data.\n\n```swift\nfunc observeContactChanges() {\n    NotificationCenter.default.addObserver(\n        forName: .CNContactStoreDidChange,\n        object: nil,\n        queue: .main\n    ) { _ in\n        // Refetch contacts -- cached CNContact objects are stale\n        refreshContacts()\n    }\n}\n```\n\n## Common Mistakes\n\n### DON'T: Fetch all keys when you only need a name\n\nOver-fetching wastes memory and slows queries, especially for contacts with\nlarge photos.\n\n```swift\n// WRONG: Fetches everything including full-resolution photos\nlet keys: [CNKeyDescriptor] = [CNContactCompleteNameKey as CNKeyDescriptor,\n    CNContactImageDataKey as CNKeyDescriptor,\n    CNContactPhoneNumbersKey as CNKeyDescriptor,\n    CNContactEmailAddressesKey as CNKeyDescriptor,\n    CNContactPostalAddressesKey as CNKeyDescriptor,\n    CNContactBirthdayKey as CNKeyDescriptor]\n\n// CORRECT: Fetch only what you display\nlet keys: [CNKeyDescriptor] = [\n    CNContactGivenNameKey as CNKeyDescriptor,\n    CNContactFamilyNameKey as CNKeyDescriptor\n]\n```\n\n### DON'T: Access unfetched properties\n\nAccessing a property that was not in `keysToFetch` throws\n`CNContactPropertyNotFetchedException` at runtime.\n\n```swift\n// WRONG: Only fetched name keys, now accessing phone\nlet keys: [CNKeyDescriptor] = [CNContactGivenNameKey as CNKeyDescriptor]\nlet contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)\nlet phone = contact.phoneNumbers.first // CRASH\n\n// CORRECT: Include the key you need\nlet keys: [CNKeyDescriptor] = [\n    CNContactGivenNameKey as CNKeyDescriptor,\n    CNContactPhoneNumbersKey as CNKeyDescriptor\n]\n```\n\n### DON'T: Mutate a CNContact directly\n\n`CNContact` is immutable. You must call `mutableCopy()` to get a `CNMutableContact`.\n\n```swift\n// WRONG: CNContact has no setter\nlet contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)\ncontact.givenName = \"New Name\" // Compile error\n\n// CORRECT: Create mutable copy\nguard let mutable = contact.mutableCopy() as? CNMutableContact else { return }\nmutable.givenName = \"New Name\"\n```\n\n### DON'T: Skip authorization and assume access\n\nWithout calling `requestAccess(for:)`, fetch methods return empty results or throw.\n\n```swift\n// WRONG: Jump straight to fetch\nlet contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)\n\n// CORRECT: Check or request access first\nlet granted = try await store.requestAccess(for: .contacts)\nguard granted else { return }\nlet contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)\n```\n\n### DON'T: Run heavy fetches on the main thread\n\n`enumerateContacts` performs I/O. Running it on the main thread blocks the UI.\n\n```swift\n// WRONG: Main thread enumeration\nfunc loadContacts() {\n    try store.enumerateContacts(with: request) { contact, _ in ... }\n}\n\n// CORRECT: Run on a background thread\nfunc loadContacts() async throws -> [CNContact] {\n    try await Task.detached {\n        var results: [CNContact] = []\n        try store.enumerateContacts(with: request) { contact, _ in\n            results.append(contact)\n        }\n        return results\n    }.value\n}\n```\n\n## Review Checklist\n\n- [ ] `NSContactsUsageDescription` added to Info.plist\n- [ ] `requestAccess(for: .contacts)` called before fetch or save operations\n- [ ] Authorization denial handled gracefully (guide user to Settings)\n- [ ] Only needed `CNKeyDescriptor` keys included in fetch requests\n- [ ] `CNContactFormatter.descriptorForRequiredKeys(for:)` used when formatting names\n- [ ] Mutable copy created via `mutableCopy()` before modifying contacts\n- [ ] `CNSaveRequest` used for all create/update/delete operations\n- [ ] Heavy fetches (`enumerateContacts`) run off the main thread\n- [ ] `CNContactStoreDidChange` observed to refresh cached contacts\n- [ ] `CNContactPickerViewController` used when full Contacts access is unnecessary\n- [ ] Picker predicates set before presenting the picker view controller\n- [ ] Single `CNContactStore` instance reused across the app\n\n## References\n\n- Extended patterns (multi-select picker, vCard export, search optimization): [references/contacts-patterns.md](references/contacts-patterns.md)\n- [Contacts framework](https://sosumi.ai/documentation/contacts)\n- [CNContactStore](https://sosumi.ai/documentation/contacts/cncontactstore)\n- [CNContactFetchRequest](https://sosumi.ai/documentation/contacts/cncontactfetchrequest)\n- [CNSaveRequest](https://sosumi.ai/documentation/contacts/cnsaverequest)\n- [CNMutableContact](https://sosumi.ai/documentation/contacts/cnmutablecontact)\n- [CNContactPickerViewController](https://sosumi.ai/documentation/contactsui/cncontactpickerviewcontroller)\n- [CNContactPickerDelegate](https://sosumi.ai/documentation/contactsui/cncontactpickerdelegate)\n- [Accessing the contact store](https://sosumi.ai/documentation/contacts/accessing-the-contact-store)\n- [NSContactsUsageDescription](https://sosumi.ai/documentation/bundleresources/information-property-list/nscontactsusagedescription)","tags":["contacts","framework","swift","ios","skills","dpearson2699"],"capabilities":["skill","source-dpearson2699","category-swift-ios-skills"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/contacts-framework","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"install_from":"skills.sh"}},"qualityScore":"0.300","qualityRationale":"deterministic score 0.30 from registry signals: · indexed on skills.sh · published under dpearson2699/swift-ios-skills","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T03:40:33.897Z","embedding":null,"createdAt":"2026-04-18T20:34:42.469Z","updatedAt":"2026-04-22T03:40:33.897Z","lastSeenAt":"2026-04-22T03:40:33.897Z","tsv":"'/documentation/bundleresources/information-property-list/nscontactsusagedescription)':1240 '/documentation/contacts)':1205 '/documentation/contacts/accessing-the-contact-store)':1236 '/documentation/contacts/cncontactfetchrequest)':1213 '/documentation/contacts/cncontactstore)':1209 '/documentation/contacts/cnmutablecontact)':1221 '/documentation/contacts/cnsaverequest)':1217 '/documentation/contactsui/cncontactpickerdelegate)':1229 '/documentation/contactsui/cncontactpickerviewcontroller)':1225 '0':753 '1':82 '18':208 '2':93 '26':32 '3':105 '6.3':30 'access':91,104,109,126,142,190,195,205,211,353,606,872,875,894,986,1016,1169,1230 'across':1185 'ad':1102 'add':83,110 'addit':95 'address':375,379,747 'app':90,608,1187 'appear':729 'array':372,376,380 'assum':985 'async':156,1079 'author':36,37,124,138,176,187,983,1114 'await':161,1021,1083 'background':1075 'base':225 'basic':102 'batch':232 'bind':625 'birthday':392 'block':1055 'bodi':698 'bool':158 'build':435 'button':709 'cach':777,792,1162 'call':940,988,1108 'capabl':96 'card':762 'category-swift-ios-skills' 'chang':63,66,442,505,768,774 'check':165,1013 'checklist':73,76,1100 'checkstatus':171 'class':653 'cnauthorizationstatus':172 'cncontact':120,247,281,315,334,628,671,691,793,933,935,948,1081,1087 'cncontact.predicateforcontacts':250 'cncontactbirthdaykey':391,852 'cncontactcompletenamekey':837 'cncontactemailaddresseskey':291,373,521,846 'cncontactfamilynamekey':259,288,322,366,867 'cncontactfetchrequest':327,1210 'cncontactformatter.descriptorforrequiredkeys':402,418,1130 'cncontactgivennamekey':256,285,319,363,864,899,923 'cncontactidentifierkey':568 'cncontactimagedatakey':381,840 'cncontactorganizationnamekey':395 'cncontactphonenumberskey':262,369,425,843,926 'cncontactpickerdeleg':656,1226 'cncontactpickerviewcontrol':27,123,134,597,633,636,644,668,677,739,1164,1222 'cncontactpostaladdresseskey':377,849 'cncontactpropertynotfetchedexcept':358,884 'cncontactstor':24,118,153,1182,1206 'cncontactstore.authorizationstatus':173 'cncontactstoredidchang':784,1158 'cncontactthumbnailimagedatakey':387 'cnkeydescriptor':255,258,261,264,284,287,290,293,318,321,324,423,427,520,523,567,570,836,839,842,845,848,851,854,863,866,869,898,901,922,925,928,1124 'cnlabeledvalu':465,541 'cnlabelphonenumbermobil':467 'cnlabelwork':543 'cnmutablecontact':433,459,537,584,945,974,1218 'cnphonenumb':469 'cnsaverequest':25,119,439,474,550,589,1144,1214 'com.apple.developer.contacts.notes':112 'common':67,70,359,798 'common-mistak':69 'compani':396 'compil':963 'compon':394 'composit':398 'configur':81 'contact':1,9,16,21,39,42,51,56,57,60,92,103,107,117,131,146,164,175,214,217,236,303,333,339,342,344,385,389,412,431,437,446,458,476,488,493,525,558,572,595,601,605,613,670,673,704,711,728,742,756,772,791,821,903,953,1005,1024,1030,1069,1092,1095,1107,1143,1163,1168,1201,1232 'contact-pick':59 'contact.familyname':462,708 'contact.givenname':460,707,960 'contact.mutablecopy':535,582,972 'contact.phonenumbers':464 'contact.phonenumbers.first':912 'contactpick':623,659,662,666,717 'contactpickerdidcancel':675 'contacts.append':341 'contactselectionview':685 'contactsui':122,621 'contain':481 'content':33 'context':631,632,645,646 'context.coordinator':638 'control':201,726,1180 'coordin':649,650,654 'copi':504,968,1137 'correct':855,914,965,1012,1071 'count':752 'crash':913 'creat':12,48,53,428,443,501,966,1138 'create/update/delete':1148 'createcontact':449 'creating-and-updating-contact':52 'current':166 'data':614,778 'databas':22,773 'date':393 'default':480 'delet':556 'deletecontact':561 'deni':192,194 'denial':1115 'descriptor':44,47,346,400 'detail':761 'didselect':669 'direct':196,759,934 'display':860 'dpearson2699':8 'els':538,585,975,1027 'email':374,515,545,746 'emailaddress':751 'empti':994 'entitl':98,113 'enumer':233,301,306,1062 'enumeratecontact':228,1046,1152 'error':964 'especi':819 'everyth':828 'exist':487 'explain':87 'export':1196 'extend':1189 'extern':771 'fals':696 'familynam':452,463 'fetch':11,38,41,128,216,237,272,348,405,491,802,813,827,856,890,991,1003,1041,1110,1128,1151 'fetchallcontact':313 'fetchcontact':242,277 'fetching-contact':40 'filter':720 'final':652 'first':364,1017 'format':410,750,1134 'fornam':783 'framework':2,10,1202 'full':188,383,604,831,1167 'full-resolut':382,830 'fullnam':420 'func':154,170,241,276,312,448,511,560,629,641,647,665,674,780,1063,1077 'get':943 'givennam':331,450,461 'grace':1117 'grant':141,191,210,603,1019,1026 'guard':532,579,969,1025 'guid':1118 'handl':1116 'heavi':1040,1150 'i/o':1048 'i/o-heavy':305 'id':907,957 'identifi':274,278,298,513,529,562,576 'immut':937 'import':114,116,121,618,620 'includ':829,915,1126 'info.plist':86,1104 'init':660 'instanc':1183 'intend':498 'io':4,31,207 'ispres':715 'jump':1000 'key':43,46,254,271,283,300,317,329,345,360,361,399,407,422,519,531,566,578,804,835,862,892,897,909,917,921,959,1011,1036,1125 'key-descriptor':45 'keystofetch':221,270,299,328,530,577,882,908,958,1010,1035 'label':466,542 'larg':823 'last':367 'let':151,248,253,282,316,325,416,421,457,472,518,524,533,548,565,571,580,587,598,634,657,703,737,834,861,896,902,910,920,952,970,1004,1018,1029 'limit':206 'listen':769 'loadcontact':1064,1078 'mail':378 'main':309,788,1044,1053,1060,1156 'makecoordin':648 'makeuiviewcontrol':630 'match':220,268,1008,1033 'matchingnam':251 'mdm':203 'mean':179 'memori':815 'method':992 'mistak':68,71,799 'modifi':500,1142 'multi':1192 'multi-select':1191 'must':490,939 'mutabl':503,534,552,581,591,967,971,1136 'mutable.emailaddresses.append':540 'mutable.givenname':977 'mutablecopi':941,1140 'mutat':931 'name':239,243,244,252,365,368,397,414,810,891,962,979,1135 'namekey':417,424 'need':352,408,808,919,1123 'new':436,445,961,978 'nil':478,479,679,786 'notdetermin':180 'note':108 'notificationcenter.default.addobserver':782 'nscontactsusagedescript':84,1101,1237 'nsobject':655 'nspredic':749,764 'nsstring':547 'number':371 'object':785,794 'observ':62,65,767,1159 'observecontactchang':781 'observing-chang':64 'oper':1113,1149 'optim':1198 'over-fetch':811 'parent':200,658,661,664 'parent.selectedcontact':672,678 'pattern':1190 'perform':304,1047 'persist':441 'phone':370,454,471,895,911 'photo':386,390,824,833 'pick':15,600 'picker':58,61,133,596,635,640,667,676,682,722,738,1172,1178,1194 'picker.delegate':637 'picker.predicateforenablingcontact':748 'picker.predicateforselectionofcontact':763 'predic':224,249,269,724,1009,1034,1173 'predicate-bas':223 'present':1176 'privat':688,693 'project':80 'prompt':169,185 'properti':350,356,362,496,507,874,877 'queri':226,818 'queue':787 'read/write':189 'receiv':609 'refer':77,78,1188 'references/contacts-patterns.md':1199,1200 'refetch':790 'refresh':776,1161 'refreshcontact':797 'request':125,326,338,1015,1068,1091,1129 'request.sortorder':330 'requestaccess':155,989,1105 'requir':100,137 'resolut':384,832 'restrict':199,204 'result':995,1086,1097 'results.append':1094 'return':159,265,294,343,539,586,639,757,976,993,1028,1096 'reus':1184 'review':72,75,1099 'review-checklist':74 'run':1039,1049,1072,1153 'runtim':886 'save':130,509,1112 'saverequest':473,484,549,555,588,594 'saverequest.add':475 'saverequest.delete':590 'saverequest.update':551 'search':1197 'select':149,213,612,710,735,754,1193 'selectedcontact':627,690,705,718,719 'self':651 'self.parent':663 'set':198,1121,1174 'setter':951 'setup':34,35,79 'sheet':714 'show':741 'showpick':695,712,716 'singl':1181 'skill':5,6 'skip':982 'slow':817 'sosumi.ai':1204,1208,1212,1216,1220,1224,1228,1235,1239 'sosumi.ai/documentation/bundleresources/information-property-list/nscontactsusagedescription)':1238 'sosumi.ai/documentation/contacts)':1203 'sosumi.ai/documentation/contacts/accessing-the-contact-store)':1234 'sosumi.ai/documentation/contacts/cncontactfetchrequest)':1211 'sosumi.ai/documentation/contacts/cncontactstore)':1207 'sosumi.ai/documentation/contacts/cnmutablecontact)':1219 'sosumi.ai/documentation/contacts/cnsaverequest)':1215 'sosumi.ai/documentation/contactsui/cncontactpickerdelegate)':1227 'sosumi.ai/documentation/contactsui/cncontactpickerviewcontroller)':1223 'source-dpearson2699' 'stale':796 'state':177,687,692 'status':167,178 'store':152,1233 'store.enumeratecontacts':336,1066,1089 'store.execute':483,554,593 'store.requestaccess':162,1022 'store.unifiedcontact':296,527,574,905,955 'store.unifiedcontacts':267,1007,1032 'straight':1001 'string':245,279,451,453,455,514,516,563 'stringvalu':470 'struct':622,684 'swift':3,29,115,150,240,275,311,415,447,510,559,617,683,736,779,825,887,946,998,1058 'swiftui':615,619 'system':140 'target':28 'task.detached':1084 'text':706 'thread':310,1045,1054,1061,1076,1157 'throw':157,246,280,314,357,456,517,564,883,997,1080 'thumbnail':388 'tocontainerwithidentifi':477 'tri':160,266,295,335,482,526,553,573,592,904,954,1006,1020,1031,1065,1082,1088 'true':713,766 'ui':1057 'uiviewcontrol':643 'uiviewcontrollerrepresent':624 'unfetch':355,873 'unifiedcontact':219 'unnecessari':1171 'updat':13,50,55,430,485 'updatecontactemail':512 'updateuiviewcontrol':642 'use':23,218,227,401,432,680,723,1132,1145,1165 'user':19,148,181,193,209,599,733,1119 'usingblock':230 'valu':468,544,765,1098 'var':332,626,689,694,697,1085 'vcard':1195 'via':1139 'view':686,700,1179 'vstack':701 'wast':814 'withidentifi':297,528,575,906,956 'without':168,602,987 'wrapper':616 'wrong':826,888,947,999,1059 'yet':186","prices":[{"id":"364a3888-3ba4-48f0-941e-2f9b8d3920a7","listingId":"6ccb1f7a-f5d0-4788-bb95-30ff26794ec6","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:34:42.469Z"}],"sources":[{"listingId":"6ccb1f7a-f5d0-4788-bb95-30ff26794ec6","source":"github","sourceId":"dpearson2699/swift-ios-skills/contacts-framework","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/contacts-framework","isPrimary":false,"firstSeenAt":"2026-04-18T22:00:50.985Z","lastSeenAt":"2026-04-22T00:53:42.140Z"},{"listingId":"6ccb1f7a-f5d0-4788-bb95-30ff26794ec6","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/contacts-framework","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/contacts-framework","isPrimary":true,"firstSeenAt":"2026-04-18T20:34:42.469Z","lastSeenAt":"2026-04-22T03:40:33.897Z"}],"details":{"listingId":"6ccb1f7a-f5d0-4788-bb95-30ff26794ec6","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"contacts-framework","source":"skills_sh","category":"swift-ios-skills","skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/contacts-framework"},"updatedAt":"2026-04-22T03:40:33.897Z"}}