Integrazione Chiamate VoIP con PushKit & CallKit in SwiftUI

A differenza delle normali notifiche push, le chiamate audio/video in tempo reale richiedono che il dispositivo mostri la schermata di chiamata nativa a tutto schermo di iOS (CallKit), con i pulsanti verdi e rossi di risposta/rifiuto, anche quando l'iPhone è bloccato o l'app è stata completamente terminata. Questo risultato si ottiene combinando PushKit (VoIP Push) e CallKit (CXProvider).

1. Registrazione del Token VoIP con PushKit

In Swift, PKPushRegistry gestisce la generazione del token VoIP ad alta priorità:

import PushKit
import CallKit

public final class VoIPCallManager: NSObject, PKPushRegistryDelegate, CXProviderDelegate {
    public static let shared = VoIPCallManager()
    private var voipRegistry: PKPushRegistry?
    private var callProvider: CXProvider?
    
    public func start() {
        voipRegistry = PKPushRegistry(queue: .main)
        voipRegistry?.delegate = self
        voipRegistry?.desiredPushTypes = [.voIP]
        
        let config = CXProviderConfiguration()
        config.supportsVideo = false
        config.maximumCallsPerCallGroup = 1
        config.supportedHandleTypes = [.generic]
        
        callProvider = CXProvider(configuration: config)
        callProvider?.setDelegate(self, queue: nil)
    }
    
    public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        let token = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
        // Invia il token VoIP con topic .voip al backend Laravel
        Task {
            try? await ApiClient.shared.post("/api/voip/register-token", body: ["token": token])
        }
    }
    
    public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completionHandler: @escaping () -> Void) {
        // Regola obbligatoria Apple: segnalare immediatamente la chiamata a CallKit
        let uuid = UUID()
        let callerName = payload.dictionaryPayload["callerName"] as? String ?? "Chiamata in arrivo"
        
        let update = CXCallUpdate()
        update.remoteHandle = CXHandle(type: .generic, value: callerName)
        update.hasVideo = false
        
        callProvider?.reportNewIncomingCall(with: uuid, update: update) { _ in
            completionHandler()
        }
    }
    
    public func providerDidReset(_ provider: CXProvider) {}
}

Vuoi integrare chiamate audio/video WebRTC o sessioni live nella tua app?

Sviluppiamo soluzioni VoIP native conformi agli standard Apple, con supporto per CallKit, Bluetooth hands-free e audio a bassa latenza.