{"id":"f528bbf3-b20c-46d2-a8a2-0a42dd1258b6","shortId":"bvU2ue","kind":"skill","title":"callkit","tagline":"Implement VoIP calling with CallKit and PushKit. Use when building incoming/outgoing call flows, registering for VoIP push notifications, configuring CXProvider and CXCallController, handling call actions, coordinating audio sessions, or creating Call Directory extensions for cal","description":"# CallKit\n\nBuild VoIP calling features that integrate with the native iOS call UI using\nCallKit and PushKit. Covers incoming/outgoing call flows, VoIP push\nregistration, audio session coordination, and call directory extensions.\nTargets Swift 6.3 / iOS 26+.\n\n## Contents\n\n- [Setup](#setup)\n- [Provider Configuration](#provider-configuration)\n- [Incoming Call Flow](#incoming-call-flow)\n- [Outgoing Call Flow](#outgoing-call-flow)\n- [PushKit VoIP Registration](#pushkit-voip-registration)\n- [Audio Session Coordination](#audio-session-coordination)\n- [Call Directory Extension](#call-directory-extension)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n## Setup\n\n### Project Configuration\n\n1. Enable the **Voice over IP** background mode in Signing & Capabilities\n2. Add the **Push Notifications** capability\n3. For call directory extensions, add a **Call Directory Extension** target\n\n### Key Types\n\n| Type | Role |\n|---|---|\n| `CXProvider` | Reports calls to the system, receives call actions |\n| `CXCallController` | Requests call actions (start, end, hold, mute) |\n| `CXCallUpdate` | Describes call metadata (caller name, video, handle) |\n| `CXProviderDelegate` | Handles system call actions and audio session events |\n| `PKPushRegistry` | Registers for and receives VoIP push notifications |\n\n## Provider Configuration\n\nCreate a single `CXProvider` at app launch and keep it alive for the app\nlifetime. Configure it with a `CXProviderConfiguration` that describes your\ncalling capabilities.\n\n```swift\nimport CallKit\n\n/// CXProvider dispatches all delegate calls to the queue passed to `setDelegate(_:queue:)`.\n/// The `let` properties are initialized once and never mutated, making this type\n/// safe to share across concurrency domains despite @unchecked Sendable.\nfinal class CallManager: NSObject, @unchecked Sendable {\n    static let shared = CallManager()\n\n    let provider: CXProvider\n    let callController = CXCallController()\n\n    private override init() {\n        let config = CXProviderConfiguration()\n        config.localizedName = \"My VoIP App\"\n        config.supportsVideo = true\n        config.maximumCallsPerCallGroup = 1\n        config.maximumCallGroups = 2\n        config.supportedHandleTypes = [.phoneNumber, .emailAddress]\n        config.includesCallsInRecents = true\n\n        provider = CXProvider(configuration: config)\n        super.init()\n        provider.setDelegate(self, queue: nil)\n    }\n}\n```\n\n## Incoming Call Flow\n\nWhen a VoIP push arrives, report the incoming call to CallKit immediately.\nThe system displays the native call UI. You must report the call before the\nPushKit completion handler returns -- failure to do so causes the system to\nterminate your app.\n\n```swift\nfunc reportIncomingCall(\n    uuid: UUID,\n    handle: String,\n    hasVideo: Bool\n) async throws {\n    let update = CXCallUpdate()\n    update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)\n    update.hasVideo = hasVideo\n    update.localizedCallerName = \"Jane Doe\"\n\n    try await withCheckedThrowingContinuation {\n        (continuation: CheckedContinuation<Void, Error>) in\n        provider.reportNewIncomingCall(\n            with: uuid,\n            update: update\n        ) { error in\n            if let error {\n                continuation.resume(throwing: error)\n            } else {\n                continuation.resume()\n            }\n        }\n    }\n}\n```\n\n### Handling the Answer Action\n\nImplement `CXProviderDelegate` to respond when the user answers:\n\n```swift\nextension CallManager: CXProviderDelegate {\n    func providerDidReset(_ provider: CXProvider) {\n        // End all calls, reset audio\n    }\n\n    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {\n        // Configure audio, connect to call server\n        configureAudioSession()\n        connectToCallServer(callUUID: action.callUUID)\n        action.fulfill()\n    }\n\n    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {\n        disconnectFromCallServer(callUUID: action.callUUID)\n        action.fulfill()\n    }\n}\n```\n\n## Outgoing Call Flow\n\nUse `CXCallController` to request an outgoing call. The system routes the\nrequest through your `CXProviderDelegate`.\n\n```swift\nfunc startOutgoingCall(handle: String, hasVideo: Bool) {\n    let uuid = UUID()\n    let handle = CXHandle(type: .phoneNumber, value: handle)\n    let startAction = CXStartCallAction(call: uuid, handle: handle)\n    startAction.isVideo = hasVideo\n\n    let transaction = CXTransaction(action: startAction)\n    callController.request(transaction) { error in\n        if let error {\n            print(\"Failed to start call: \\(error)\")\n        }\n    }\n}\n```\n\n### Delegate Methods for Outgoing Calls\n\n```swift\nextension CallManager {\n    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {\n        configureAudioSession()\n        // Begin connecting to server\n        provider.reportOutgoingCall(\n            with: action.callUUID,\n            startedConnectingAt: Date()\n        )\n\n        connectToServer(callUUID: action.callUUID) {\n            provider.reportOutgoingCall(\n                with: action.callUUID,\n                connectedAt: Date()\n            )\n        }\n        action.fulfill()\n    }\n}\n```\n\n## PushKit VoIP Registration\n\nRegister for VoIP pushes at every app launch. Send the token to your server\nwhenever it changes.\n\n```swift\nimport PushKit\n\nfinal class PushManager: NSObject, PKPushRegistryDelegate {\n    let registry: PKPushRegistry\n\n    override init() {\n        registry = PKPushRegistry(queue: .main)\n        super.init()\n        registry.delegate = self\n        registry.desiredPushTypes = [.voIP]\n    }\n\n    func pushRegistry(\n        _ registry: PKPushRegistry,\n        didUpdate pushCredentials: PKPushCredentials,\n        for type: PKPushType\n    ) {\n        let token = pushCredentials.token\n            .map { String(format: \"%02x\", $0) }\n            .joined()\n        // Send token to your server\n        sendTokenToServer(token)\n    }\n\n    func pushRegistry(\n        _ registry: PKPushRegistry,\n        didReceiveIncomingPushWith payload: PKPushPayload,\n        for type: PKPushType,\n        completion: @escaping () -> Void\n    ) {\n        guard type == .voIP else {\n            completion()\n            return\n        }\n\n        let callUUID = UUID()\n        let handle = payload.dictionaryPayload[\"handle\"] as? String ?? \"Unknown\"\n\n        Task {\n            do {\n                try await CallManager.shared.reportIncomingCall(\n                    uuid: callUUID,\n                    handle: handle,\n                    hasVideo: false\n                )\n            } catch {\n                // Call was filtered by DND or block list\n            }\n            completion()\n        }\n    }\n}\n```\n\n## Audio Session Coordination\n\nCallKit manages audio session activation/deactivation. Configure your audio\nsession when CallKit tells you to, not before.\n\n```swift\nextension CallManager {\n    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {\n        // Audio session is now active -- start audio engine / WebRTC\n        startAudioEngine()\n    }\n\n    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {\n        // Audio session deactivated -- stop audio engine\n        stopAudioEngine()\n    }\n\n    func configureAudioSession() {\n        let session = AVAudioSession.sharedInstance()\n        do {\n            try session.setCategory(\n                .playAndRecord,\n                mode: .voiceChat,\n                options: [.allowBluetooth, .allowBluetoothA2DP]\n            )\n        } catch {\n            print(\"Audio session configuration failed: \\(error)\")\n        }\n    }\n}\n```\n\n## Call Directory Extension\n\nCreate a Call Directory extension to provide caller ID and call blocking.\n\n```swift\nimport CallKit\n\nfinal class CallDirectoryHandler: CXCallDirectoryProvider {\n    override func beginRequest(\n        with context: CXCallDirectoryExtensionContext\n    ) {\n        if context.isIncremental {\n            addOrRemoveIncrementalEntries(to: context)\n        } else {\n            addAllEntries(to: context)\n        }\n        context.completeRequest()\n    }\n\n    private func addAllEntries(\n        to context: CXCallDirectoryExtensionContext\n    ) {\n        // Phone numbers must be in ascending order (E.164 format as Int64)\n        let blockedNumbers: [CXCallDirectoryPhoneNumber] = [\n            18005551234, 18005555678\n        ]\n        for number in blockedNumbers {\n            context.addBlockingEntry(\n                withNextSequentialPhoneNumber: number\n            )\n        }\n\n        let identifiedNumbers: [(CXCallDirectoryPhoneNumber, String)] = [\n            (18005551111, \"Local Pizza\"),\n            (18005552222, \"Dentist Office\")\n        ]\n        for (number, label) in identifiedNumbers {\n            context.addIdentificationEntry(\n                withNextSequentialPhoneNumber: number,\n                label: label\n            )\n        }\n    }\n}\n```\n\nReload the extension from the main app after data changes:\n\n```swift\nCXCallDirectoryManager.sharedInstance.reloadExtension(\n    withIdentifier: \"com.example.app.CallDirectory\"\n) { error in\n    if let error { print(\"Reload failed: \\(error)\") }\n}\n```\n\n## Common Mistakes\n\n### DON'T: Fail to report a call on VoIP push receipt\n\nIf your PushKit delegate receives a VoIP push but does not call\n`reportNewIncomingCall(with:update:completion:)`, iOS terminates your app and\nmay stop delivering pushes entirely.\n\n```swift\n// WRONG -- no call reported\nfunc pushRegistry(\n    _ registry: PKPushRegistry,\n    didReceiveIncomingPushWith payload: PKPushPayload,\n    for type: PKPushType,\n    completion: @escaping () -> Void\n) {\n    // Just process data, no call reported\n    processPayload(payload)\n    completion()\n}\n\n// CORRECT -- always report a call\nfunc pushRegistry(\n    _ registry: PKPushRegistry,\n    didReceiveIncomingPushWith payload: PKPushPayload,\n    for type: PKPushType,\n    completion: @escaping () -> Void\n) {\n    let uuid = UUID()\n    provider.reportNewIncomingCall(\n        with: uuid, update: makeUpdate(from: payload)\n    ) { _ in completion() }\n}\n```\n\n### DON'T: Start audio before CallKit activates the session\n\nStarting your audio engine before `provider(_:didActivate:)` causes silence\nor immediate deactivation. CallKit manages session priority with the system.\n\n```swift\n// WRONG\nfunc provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {\n    startAudioEngine()  // Too early -- session not active yet\n    action.fulfill()\n}\n\n// CORRECT\nfunc provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {\n    prepareAudioEngine()  // Prepare, but do not start\n    action.fulfill()\n}\n\nfunc provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {\n    startAudioEngine()  // Now it's safe\n}\n```\n\n### DON'T: Forget to call action.fulfill() or action.fail()\n\nFailing to fulfill or fail an action leaves the call in a limbo state and\ntriggers the timeout handler.\n\n```swift\n// WRONG\nfunc provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {\n    connectToServer()\n    // Forgot action.fulfill()\n}\n\n// CORRECT\nfunc provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {\n    connectToServer()\n    action.fulfill()\n}\n```\n\n### DON'T: Ignore push token refresh\n\nThe VoIP push token can change at any time. If your server has a stale token,\npushes silently fail and incoming calls never arrive.\n\n```swift\n// WRONG -- only send token once at first registration\nfunc pushRegistry(\n    _ registry: PKPushRegistry,\n    didUpdate pushCredentials: PKPushCredentials,\n    for type: PKPushType\n) {\n    // Token saved locally but never updated on server\n}\n\n// CORRECT -- always update server\nfunc pushRegistry(\n    _ registry: PKPushRegistry,\n    didUpdate pushCredentials: PKPushCredentials,\n    for type: PKPushType\n) {\n    let token = pushCredentials.token.map { String(format: \"%02x\", $0) }.joined()\n    sendTokenToServer(token)  // Always send to server\n}\n```\n\n## Review Checklist\n\n- [ ] VoIP background mode enabled in capabilities\n- [ ] Single `CXProvider` instance created at app launch and retained\n- [ ] `CXProviderDelegate` set before reporting any calls\n- [ ] Every VoIP push results in a `reportNewIncomingCall` call\n- [ ] `action.fulfill()` or `action.fail()` called for every provider delegate action\n- [ ] Audio engine started only after `provider(_:didActivate:)` callback\n- [ ] Audio engine stopped in `provider(_:didDeactivate:)` callback\n- [ ] Audio session category set to `.playAndRecord` with `.voiceChat` mode\n- [ ] VoIP push token sent to server on every `didUpdate pushCredentials` callback\n- [ ] `PKPushRegistry` created at every app launch (not lazily)\n- [ ] Call Directory phone numbers added in ascending E.164 order\n- [ ] `CXCallUpdate` populated with `localizedCallerName` and `remoteHandle`\n- [ ] Outgoing calls report `startedConnectingAt` and `connectedAt` timestamps\n\n## References\n\n- Extended patterns (hold, mute, group calls, delegate lifecycle): [references/callkit-patterns.md](references/callkit-patterns.md)\n- [CallKit framework](https://sosumi.ai/documentation/callkit)\n- [CXProvider](https://sosumi.ai/documentation/callkit/cxprovider)\n- [CXCallController](https://sosumi.ai/documentation/callkit/cxcallcontroller)\n- [CXCallAction](https://sosumi.ai/documentation/callkit/cxcallaction)\n- [CXCallUpdate](https://sosumi.ai/documentation/callkit/cxcallupdate)\n- [CXProviderConfiguration](https://sosumi.ai/documentation/callkit/cxproviderconfiguration)\n- [CXProviderDelegate](https://sosumi.ai/documentation/callkit/cxproviderdelegate)\n- [PKPushRegistry](https://sosumi.ai/documentation/pushkit/pkpushregistry)\n- [PKPushRegistryDelegate](https://sosumi.ai/documentation/pushkit/pkpushregistrydelegate)\n- [CXCallDirectoryProvider](https://sosumi.ai/documentation/callkit/cxcalldirectoryprovider)\n- [Making and receiving VoIP calls](https://sosumi.ai/documentation/callkit/making-and-receiving-voip-calls)\n- [Responding to VoIP Notifications from PushKit](https://sosumi.ai/documentation/pushkit/responding-to-voip-notifications-from-pushkit)","tags":["callkit","swift","ios","skills","dpearson2699","accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills","cursor-skills"],"capabilities":["skill","source-dpearson2699","skill-callkit","topic-accessibility","topic-agent-skills","topic-ai-coding","topic-apple","topic-claude-code","topic-codex-skills","topic-cursor-skills","topic-ios","topic-ios-development","topic-liquid-glass","topic-localization","topic-mapkit"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/callkit","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add dpearson2699/swift-ios-skills","source_repo":"https://github.com/dpearson2699/swift-ios-skills","install_from":"skills.sh"}},"qualityScore":"0.684","qualityRationale":"deterministic score 0.68 from registry signals: · indexed on github topic:agent-skills · 468 github stars · SKILL.md body (14,212 chars)","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-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T00:53:41.915Z","embedding":null,"createdAt":"2026-04-18T22:00:48.711Z","updatedAt":"2026-04-22T00:53:41.915Z","lastSeenAt":"2026-04-22T00:53:41.915Z","tsv":"'/documentation/callkit)':1280 '/documentation/callkit/cxcallaction)':1292 '/documentation/callkit/cxcallcontroller)':1288 '/documentation/callkit/cxcalldirectoryprovider)':1316 '/documentation/callkit/cxcallupdate)':1296 '/documentation/callkit/cxprovider)':1284 '/documentation/callkit/cxproviderconfiguration)':1300 '/documentation/callkit/cxproviderdelegate)':1304 '/documentation/callkit/making-and-receiving-voip-calls)':1324 '/documentation/pushkit/pkpushregistry)':1308 '/documentation/pushkit/pkpushregistrydelegate)':1312 '/documentation/pushkit/responding-to-voip-notifications-from-pushkit)':1333 '0':615,1152 '02x':614,1151 '1':131,297 '18005551111':819 '18005551234':806 '18005552222':822 '18005555678':807 '2':142,299 '26':72 '3':148 '6.3':70 'across':262 'action':26,171,175,192,409,436,454,507,535,989,1005,1040,1060,1071,1199 'action.calluuid':447,458,544,549,552 'action.fail':1033,1193 'action.fulfill':448,459,555,998,1013,1031,1064,1074,1191 'activ':707,960,996 'activation/deactivation':681 'ad':1247 'add':143,153 'addallentri':782,788 'addorremoveincrementalentri':778 'aliv':217 'allowbluetooth':739 'allowbluetootha2dp':740 'alway':925,1133,1156 'answer':408,417 'app':212,220,293,357,565,841,890,1173,1239 'arriv':321,1104 'ascend':797,1249 'async':367 'audio':28,61,102,106,194,430,439,674,679,684,703,709,720,724,743,957,965,1200,1208,1215 'audio-session-coordin':105 'audiosess':701,718,1019 'avaudiosess':702,719,1020 'avaudiosession.sharedinstance':731 'await':384,656 'background':137,1163 'begin':538 'beginrequest':772 'block':671,762 'blockednumb':804,811 'bool':366,484 'build':11,38 'cal':36 'call':4,13,25,32,40,48,56,65,82,86,89,93,109,113,150,155,165,170,174,182,191,230,239,315,325,334,340,428,442,461,469,498,520,526,665,748,753,761,866,882,900,919,928,1030,1043,1102,1182,1190,1194,1243,1259,1271,1321 'call-directory-extens':112 'callback':1207,1214,1234 'callcontrol':282 'callcontroller.request':509 'calldirectoryhandl':768 'caller':184,758 'callkit':1,6,37,51,234,327,677,687,765,959,975,1276 'callmanag':270,277,420,529,695 'callmanager.shared.reportincomingcall':657 'calluuid':446,457,548,644,659 'capabl':141,147,231,1167 'catch':664,741 'categori':1217 'caus':351,970 'chang':575,844,1086 'checkedcontinu':387 'checklist':122,125,1161 'class':269,580,767 'com.example.app.calldirectory':848 'common':116,119,858 'common-mistak':118 'complet':344,634,641,673,886,912,923,939,953 'concurr':263 'config':288,308 'config.includescallsinrecents':303 'config.localizedname':290 'config.maximumcallgroups':298 'config.maximumcallspercallgroup':296 'config.supportedhandletypes':300 'config.supportsvideo':294 'configur':20,77,80,130,206,222,307,438,682,745 'configureaudiosess':444,537,728 'connect':440,539 'connectedat':553,1263 'connecttocallserv':445 'connecttoserv':547,1062,1073 'content':73 'context':774,780,784,790 'context.addblockingentry':812 'context.addidentificationentry':830 'context.completerequest':785 'context.isincremental':777 'continu':386 'continuation.resume':401,405 'coordin':27,63,104,108,676 'correct':924,999,1065,1132 'cover':54 'creat':31,207,751,1171,1236 'cxanswercallact':437,990,1006,1061,1072 'cxcallact':1289 'cxcallcontrol':23,172,283,464,1285 'cxcalldirectoryextensioncontext':775,791 'cxcalldirectorymanager.sharedinstance.reloadextension':846 'cxcalldirectoryphonenumb':805,817 'cxcalldirectoryprovid':769,1313 'cxcallupd':180,371,1252,1293 'cxendcallact':455 'cxhandl':373,490 'cxprovid':21,163,210,235,280,306,425,434,452,533,699,716,987,1003,1017,1058,1069,1169,1281 'cxproviderconfigur':226,289,1297 'cxproviderdeleg':188,411,421,477,1177,1301 'cxstartcallact':497,536 'cxtransact':506 'data':843,917 'date':546,554 'deactiv':722,974 'deleg':238,522,874,1198,1272 'deliv':894 'dentist':823 'describ':181,228 'despit':265 'didactiv':700,969,1018,1206 'diddeactiv':717,1213 'didreceiveincomingpushwith':628,906,933 'didupd':602,1118,1140,1232 'directori':33,66,110,114,151,156,749,754,1244 'disconnectfromcallserv':456 'dispatch':236 'display':331 'dnd':669 'doe':382 'domain':264 'e.164':799,1250 'earli':993 'els':404,640,781 'emailaddress':302 'enabl':132,1165 'end':177,426 'engin':710,725,966,1201,1209 'entir':896 'error':389,396,400,403,511,515,521,747,849,853,857 'escap':635,913,940 'event':196 'everi':564,1183,1196,1231,1238 'extend':1266 'extens':34,67,111,115,152,157,419,528,694,750,755,837 'fail':517,746,856,862,1034,1038,1099 'failur':347 'fals':663 'featur':41 'filter':667 'final':268,579,766 'first':1112 'flow':14,57,83,87,90,94,316,462 'forget':1028 'forgot':1063 'format':613,800,1150 'framework':1277 'fulfil':1036 'func':359,422,431,449,479,530,598,624,696,713,727,771,787,902,929,984,1000,1014,1055,1066,1114,1136 'group':1270 'guard':637 'handl':24,187,189,363,377,406,481,489,494,500,501,647,649,660,661 'handler':345,1052 'hasvideo':365,379,483,503,662 'hold':178,1268 'id':759 'identifiednumb':816,829 'ignor':1077 'immedi':328,973 'implement':2,410 'import':233,577,764 'incom':81,85,314,324,1101 'incoming-call-flow':84 'incoming/outgoing':12,55 'init':286,588 'initi':251 'instanc':1170 'int64':802 'integr':43 'io':47,71,887 'ip':136 'jane':381 'join':616,1153 'keep':215 'key':159 'label':827,833,834 'launch':213,566,1174,1240 'lazili':1242 'leav':1041 'let':248,275,278,281,287,369,399,485,488,495,504,514,584,608,643,646,729,803,815,852,942,1146 'lifecycl':1273 'lifetim':221 'limbo':1046 'list':672 'local':820,1126 'localizedcallernam':1255 'main':592,840 'make':256,1317 'makeupd':949 'manag':678,976 'map':611 'may':892 'metadata':183 'method':523 'mistak':117,120,859 'mode':138,736,1164,1223 'must':337,794 'mutat':255 'mute':179,1269 'name':185 'nativ':46,333 'never':254,1103,1128 'nil':313 'notif':19,146,204,1328 'nsobject':271,582 'number':793,809,814,826,832,1246 'offic':824 'option':738 'order':798,1251 'outgo':88,92,460,468,525,1258 'outgoing-call-flow':91 'overrid':285,587,770 'pass':243 'pattern':1267 'payload':629,907,922,934,951 'payload.dictionarypayload':648 'perform':435,453,534,988,1004,1059,1070 'phone':792,1245 'phonenumb':301,375,492 'pizza':821 'pkpushcredenti':604,1120,1142 'pkpushpayload':630,908,935 'pkpushregistri':197,586,590,601,627,905,932,1117,1139,1235,1305 'pkpushregistrydeleg':583,1309 'pkpushtyp':607,633,911,938,1123,1145 'playandrecord':735,1220 'popul':1253 'prepar':1008 'prepareaudioengin':1007 'print':516,742,854 'prioriti':978 'privat':284,786 'process':916 'processpayload':921 'project':129 'properti':249 'provid':76,79,205,279,305,424,432,433,450,451,531,532,697,698,714,715,757,968,985,986,1001,1002,1015,1016,1056,1057,1067,1068,1197,1205,1212 'provider-configur':78 'provider.reportnewincomingcall':391,945 'provider.reportoutgoingcall':542,550 'provider.setdelegate':310 'providerdidreset':423 'push':18,59,145,203,320,562,869,878,895,1078,1083,1097,1185,1225 'pushcredenti':603,1119,1141,1233 'pushcredentials.token':610 'pushcredentials.token.map':1148 'pushkit':8,53,95,99,343,556,578,873,1330 'pushkit-voip-registr':98 'pushmanag':581 'pushregistri':599,625,903,930,1115,1137 'queue':242,246,312,591 'receipt':870 'receiv':169,201,875,1319 'refer':126,127,1265 'references/callkit-patterns.md':1274,1275 'refresh':1080 'regist':15,198,559 'registr':60,97,101,558,1113 'registri':585,589,600,626,904,931,1116,1138 'registry.delegate':594 'registry.desiredpushtypes':596 'reload':835,855 'remotehandl':1257 'report':164,322,338,864,901,920,926,1180,1260 'reportincomingcal':360 'reportnewincomingcal':883,1189 'request':173,466,474 'reset':429 'respond':413,1325 'result':1186 'retain':1176 'return':346,642 'review':121,124,1160 'review-checklist':123 'role':162 'rout':472 'safe':259,1025 'save':1125 'self':311,595 'send':567,617,1108,1157 'sendabl':267,273 'sendtokentoserv':622,1154 'sent':1227 'server':443,541,572,621,1092,1131,1135,1159,1229 'session':29,62,103,107,195,675,680,685,704,721,730,744,962,977,994,1216 'session.setcategory':734 'set':1178,1218 'setdeleg':245 'setup':74,75,128 'share':261,276 'sign':140 'silenc':971 'silent':1098 'singl':209,1168 'skill' 'skill-callkit' 'sosumi.ai':1279,1283,1287,1291,1295,1299,1303,1307,1311,1315,1323,1332 'sosumi.ai/documentation/callkit)':1278 'sosumi.ai/documentation/callkit/cxcallaction)':1290 'sosumi.ai/documentation/callkit/cxcallcontroller)':1286 'sosumi.ai/documentation/callkit/cxcalldirectoryprovider)':1314 'sosumi.ai/documentation/callkit/cxcallupdate)':1294 'sosumi.ai/documentation/callkit/cxprovider)':1282 'sosumi.ai/documentation/callkit/cxproviderconfiguration)':1298 'sosumi.ai/documentation/callkit/cxproviderdelegate)':1302 'sosumi.ai/documentation/callkit/making-and-receiving-voip-calls)':1322 'sosumi.ai/documentation/pushkit/pkpushregistry)':1306 'sosumi.ai/documentation/pushkit/pkpushregistrydelegate)':1310 'sosumi.ai/documentation/pushkit/responding-to-voip-notifications-from-pushkit)':1331 'source-dpearson2699' 'stale':1095 'start':176,519,708,956,963,1012,1202 'startact':496,508 'startaction.isvideo':502 'startaudioengin':712,991,1021 'startedconnectingat':545,1261 'startoutgoingcal':480 'state':1047 'static':274 'stop':723,893,1210 'stopaudioengin':726 'string':364,482,612,651,818,1149 'super.init':309,593 'swift':69,232,358,418,478,527,576,693,763,845,897,982,1053,1105 'system':168,190,330,353,471,981 'target':68,158 'task':653 'tell':688 'termin':355,888 'throw':368,402 'time':1089 'timeout':1051 'timestamp':1264 'token':569,609,618,623,1079,1084,1096,1109,1124,1147,1155,1226 'topic-accessibility' 'topic-agent-skills' 'topic-ai-coding' 'topic-apple' 'topic-claude-code' 'topic-codex-skills' 'topic-cursor-skills' 'topic-ios' 'topic-ios-development' 'topic-liquid-glass' 'topic-localization' 'topic-mapkit' 'transact':505,510 'tri':383,655,733 'trigger':1049 'true':295,304 'type':160,161,258,374,491,606,632,638,910,937,1122,1144 'ui':49,335 'uncheck':266,272 'unknown':652 'updat':370,394,395,885,948,1129,1134 'update.hasvideo':378 'update.localizedcallername':380 'update.remotehandle':372 'use':9,50,463 'user':416 'uuid':361,362,393,486,487,499,645,658,943,944,947 'valu':376,493 'video':186 'voic':134 'voicechat':737,1222 'void':388,636,914,941 'voip':3,17,39,58,96,100,202,292,319,557,561,597,639,868,877,1082,1162,1184,1224,1320,1327 'webrtc':711 'whenev':573 'withcheckedthrowingcontinu':385 'withidentifi':847 'withnextsequentialphonenumb':813,831 'wrong':898,983,1054,1106 'yet':997","prices":[{"id":"c41176af-029a-428b-ba94-c5aea9821edf","listingId":"f528bbf3-b20c-46d2-a8a2-0a42dd1258b6","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-18T22:00:48.711Z"}],"sources":[{"listingId":"f528bbf3-b20c-46d2-a8a2-0a42dd1258b6","source":"github","sourceId":"dpearson2699/swift-ios-skills/callkit","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/callkit","isPrimary":false,"firstSeenAt":"2026-04-18T22:00:48.711Z","lastSeenAt":"2026-04-22T00:53:41.915Z"}],"details":{"listingId":"f528bbf3-b20c-46d2-a8a2-0a42dd1258b6","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"callkit","github":{"repo":"dpearson2699/swift-ios-skills","stars":468,"topics":["accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills","cursor-skills","ios","ios-development","liquid-glass","localization","mapkit","networking","storekit","swift","swift-concurrency","swiftdata","swiftui","widgetkit","xcode"],"license":"other","html_url":"https://github.com/dpearson2699/swift-ios-skills","pushed_at":"2026-04-21T19:26:16Z","description":"Agent Skills for iOS 26+, Swift 6.3, SwiftUI, and modern Apple frameworks","skill_md_sha":"4aa2a2e999d155ce94ec839cbcb521ae28016c1d","skill_md_path":"skills/callkit/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/callkit"},"layout":"multi","source":"github","category":"swift-ios-skills","frontmatter":{"name":"callkit","description":"Implement VoIP calling with CallKit and PushKit. Use when building incoming/outgoing call flows, registering for VoIP push notifications, configuring CXProvider and CXCallController, handling call actions, coordinating audio sessions, or creating Call Directory extensions for caller ID and call blocking."},"skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/callkit"},"updatedAt":"2026-04-22T00:53:41.915Z"}}