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:

  1. PushKit (VoIP Push Notifications): Notifiche push ad altissima priorità che risvegliano il processo dell'app in background in meno di 100 millisecondi.
  2. 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.

Diallo Yunus

Diallo Yunus

Indie iOS & macOS Developer, Fondatore di DialloDev e autore di 11+ app pubblicate su Apple App Store. Aiuto startup, creator e aziende a costruire prodotti digitali di successo.