Come Funzionano le Chiamate VoIP su iOS con CallKit
Sui dispositivi Apple, un'app di messaggistica o chiamate non può rimanere perennemente attiva in background. Per ricevere chiamate vocali quando l'app è chiusa o l'iPhone è bloccato, Apple impone una rigida architettura a due pilastri:
- PushKit (VoIP Push Notifications): Notifiche push ad altissima priorità che risvegliano il processo dell'app in background in meno di 100 millisecondi.
- CallKit (CXProvider): Il framework nativo che mostra l'interfaccia di chiamata a schermo intero identica all'app Telefono di Apple, con nome chiamante, pulsanti Rispondi/Rifiuta e gestione audio Bluetooth/CarPlay.
⚠️ Regola Mandatoria di Apple per l'App Store:
Ogni volta che l'app riceve una notifica PushKit VoIP, è OBBLIGATORIO notificare immediatamente CallKit tramite reportNewIncomingCall. Se ricevi una notifica PushKit e non mostri l'interfaccia CallKit entro 5 secondi, iOS terminerà l'app e Apple bloccherà i permessi VoIP.
Codice Pratico: CXProviderDelegate e Gestione Chiamate in Swift
import CallKit
import PushKit
import AVFoundation
final class VoIPCallManager: NSObject, CXProviderDelegate, PKPushRegistryDelegate {
static let shared = VoIPCallManager()
private let provider: CXProvider
private let callController = CXCallController()
override init() {
let configuration = CXProviderConfiguration(localizedName: "DialloDev VoIP")
configuration.supportsVideo = true
configuration.maximumCallsPerCallGroup = 1
configuration.supportedHandleTypes = [.phoneNumber, .generic]
configuration.ringtoneSound = "ringtone.caf"
self.provider = CXProvider(configuration: configuration)
super.init()
self.provider.setDelegate(self, queue: nil)
setupPushKit()
}
private func setupPushKit() {
let pushRegistry = PKPushRegistry(queue: DispatchQueue.main)
pushRegistry.delegate = self
pushRegistry.desiredPushTypes = [.voIP]
}
// MARK: - PushKit Delegate
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) async {
guard type == .voIP,
let callerName = payload.dictionaryPayload["caller_name"] as? String,
let callUUIDString = payload.dictionaryPayload["call_uuid"] as? String,
let callUUID = UUID(uuidString: callUUIDString) else { return }
// Risveglia CallKit a schermo bloccato
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: callerName)
update.localizedCallerName = callerName
update.hasVideo = false
do {
try await provider.reportNewIncomingCall(with: callUUID, update: update)
} catch {
print("Errore segnalazione chiamata a CallKit: \(error.localizedDescription)")
}
}
// MARK: - CXProviderDelegate
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// L'utente ha risposto: inizializza audio WebRTC o socket
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
// L'utente ha chiuso o rifiutato la chiamata
action.fulfill()
}
func providerDidReset(_ provider: CXProvider) {
// Ripristina lo stato audio
}
}
Vuoi creare la tua App o hai bisogno di consulenza personalizzata?
Sviluppo app iOS native in SwiftUI, piattaforme backend scalabili e offro supporto strategico per monetizzazione e progetti internazionali.