Реалізація CallKit для iOS (інтеграція з системними звонками)
CallKit дозволяє застосунку відображати входящі звонки так само, як системні виклики оператора: на весь екран, з ім'ям контакту, кнопками відповіді та відхилення, інтеграцією з історією звонків у «Телефоні» та блокуванням через «Заблоковані контакти». Без CallKit входящий звонок виглядає як push-сповіщення — користувач тисне «Пізніше», не розуміючи, що це звонок.
Як це працює всередину
Центральний клас — CXProvider. Він є точкою комунікації між застосунком та системою: через нього повідомляємо про входящий звонок, оновлюємо інформацію, завершуємо виклик. Другий клас — CXCallController, через який застосунок ініціює вихідні звонки та керує ними.
let providerConfiguration = CXProviderConfiguration()
providerConfiguration.supportsVideo = true
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber, .emailAddress, .generic]
provider = CXProvider(configuration: providerConfiguration)
provider.setDelegate(self, queue: nil)
Входящий звонок. Приходить через VoIP push (PushKit, не APNs). Це важливо: звичайний APNs push не підтримує CallKit-звонки з iOS 13 — Apple вимагає використовувати PKPushType.voIP та в делегаті PKPushRegistryDelegate.pushRegistry(_:didReceiveIncomingPushWith:) негайно викликати reportNewIncomingCall. Затримка між push та reportNewIncomingCall — застосунок завершується системою.
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle)
update.hasVideo = hasVideo
provider.reportNewIncomingCall(with: uuid, update: update) { error in
// якщо error != nil — система відхилила звонок (наприклад, DND)
}
}
Відповідь/відхилення. Система викликає методи CXProviderDelegate: provider(_:perform:) з CXAnswerCallAction або CXEndCallAction. У відповідь на AnswerAction потрібно підключити аудіо — запустити WebRTC сесію, підключити VoIP SDK (Twilio Voice, Agora, Daily).
WebRTC та аудіосесія
CallKit керує аудіосесією системи. Не можна налаштовувати AVAudioSession самостійно — це обов'язок CallKit. При відповіді на звонок система активує аудіосесію та викликає provider(_:didActivate:). В цей момент підключаємо аудіо-поток WebRTC до AVAudioSession. При завершенні — provider(_:didDeactivate:), відключаємо.
Якщо викликати AVAudioSession.setActive(true) раніше цього моменту — звонок може перерватися або аудіо не з'явиться. Типовий баг при першій інтеграції.
Twilio Voice SDK: TwilioVoice.handleNotification → call.accept(with: delegate) → в callDidConnect активуємо аудіо через CallKit. SDK інкапсулює частину цієї логіки, але CXProvider все ж таки потрібен свій.
Історія звонків та Siri
Після завершення звонку викликаємо provider.reportCall(with: uuid, endedAt: Date(), reason: .remoteEnded). iOS автоматично додає запис в історію звонків «Телефону» з ім'ям та тривалістю. Користувач може перезвонити через «Телефон» — це виклик через застосунок, якщо CXHandle типу .generic збігається з ідентифікатором.
Siri Shortcuts для звонків: через INStartCallIntent (iOS 13+) застосунок реєструє намір. «Позвони Івану через MyApp» — Siri запускає звонок через CallKit.
Типові проблеми
Дублюючі звонки. UUID повинен бути унікальним для кожного вишику та не змінюватися між push та відповіддю. Якщо push прийшов двічі (retry), перевіряємо UUID — не створюємо другий CXCallUpdate для того ж UUID.
Зависший звонок в історії. Якщо застосунок крашнув не викликавши reportCall(endedAt:), звонок залишається як «активний» в історії. Рішення: при наступному запуску застосунку перевіряємо CXCallObserver.calls — якщо є незавершені, завершуємо їх.
VoIP Push на iOS 13+. PKPushRegistry повинен ініціалізуватися в application(_:didFinishLaunchingWithOptions:), не ліниво. Apple перевіряє це та може завершити застосунок.
Що входить в роботу
- Налаштування
CXProviderтаCXCallControllerпід вимоги застосунку - Інтеграція PushKit для VoIP push
- Обробники відповіді, відхилення, завершення, hold, mute
- Підключення до WebRTC/VoIP SDK (Twilio Voice, Agora, Daily, Vonage)
- Аудіосесія через CallKit lifecycle
- Запис в історію звонків
- Обробка крах-сценаріїв та незавершених звонків
Строки
Базова реалізація входящого/вихідного звонку з одним VoIP SDK: 2–3 дня. З груповими звонками, відео, Siri Shortcuts та custom UI: 4–5 днів. Вартість розраховується після аналізу VoIP-інфраструктури та вимог.







