Реализация 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-инфраструктуры и требований.







