# Handling incoming calls In the previous section we added the capability of initiating the call. This section describes how to get notified of and handle incoming calls. If you haven't yet initiated a call, go back and follow the steps in that [section](/docs/in-app-calling/getting-started/ios/make-call). Sinch SDK requires APNs VoIP notifications to establish calls. Make sure you've uploaded your APNs signing keys to your Sinch application (see [Create app section](/docs/in-app-calling/getting-started/ios/create-app#upload-your-apns-signing-keys)). Starting from iOS13, incoming VoIP notifications *have* to be reported to CallKit or your app will be killed (refer to [Sinch public docs](https://developers.sinch.com/docs/in-app-calling/ios/push-notifications-callkit/#configuring-an-apns-authentication-signing-key) for further details). For clarity, this guide will first describe the procedure to report calls to CallKit, while handling of incoming VoIP notification will be showed afterwards. ## Report an incoming call to CallKit To enable push notification usage in Sinch client, instantiate a `SinchManagedPush` object as an `AppDelegate` property, and request a device token for VoIP notifications: *AppDelegate.swift* ```swift class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { ... sinchPush = SinchRTC.managedPush( forAPSEnvironment: SinchRTC.APSEnvironment.development) sinchPush.delegate = self sinchPush.setDesiredPushType(SinchManagedPush.TypeVoIP) ... return true } ``` Let's now extend AppDelegate to conform with `SinchManagedPushDelegate`, to handle incoming VoIP notifications. The implementation of `SinchClientMediator.reportIncomingCall(withPushPayload:withCompletion:)` will follow. Don't forget to forward the incoming push payload to Sinch client with `SinchClient.relayPushNotification(withUserInfo:)`, which allows Sinch client to instantiate a new `SinchCall` object based on information contained in the push payload. *AppDelegate.swift* ```swift extension AppDelegate: SinchManagedPushDelegate { func managedPush(_ managedPush: SinchManagedPush, didReceiveIncomingPushWithPayload payload: [AnyHashable: Any], for type: String) { os_log("didReceiveIncomingPushWithPayload: %{public}@", log: self.customLog, payload.description) // Request SinchClientProvider to report new call to CallKit sinchClientMediator.reportIncomingCall(withPushPayload: payload, withCompletion: { err in DispatchQueue.main.async { self.sinchClientMediator.sinchClient?.relayPushNotification( withUserInfo: payload) } if err != nil { os_log("Error when reporting call to CallKit: %{public}@", log: self.customLog, type: .error, err!.localizedDescription) } }) } } ``` *SinchClientMediator.swift* ```swift func reportIncomingCall(withPushPayload payload: [AnyHashable: Any], withCompletion completion: @escaping (Error?) -> Void) { func reportIncomingCall(withPushPayload payload: [AnyHashable: Any], withCompletion completion: @escaping (Error?) -> Void) { // Extract call information from the push payload let notification = queryPushNotificationPayload(payload) if notification.isCall && notification.isValid { let callNotification = notification.callResult guard callRegistry.callKitUUID( forSinchId: callNotification.callId) == nil else { return } let cxCallId = UUID() callRegistry.map(callKitId: cxCallId, toSinchCallId: callNotification.callId) os_log("reportNewIncomingCallToCallKit: ckid:%{public}@ callId:%{public}@", log: customLog, cxCallId.description, callNotification.callId) // Request SinchClientProvider to report new call to CallKit let update = CXCallUpdate() update.remoteHandle = CXHandle(type: .generic, value: callNotification.remoteUserId) update.hasVideo = callNotification.isVideoOffered // Reporting the call to CallKit provider.reportNewIncomingCall(with: cxCallId, update: update) { (error: Error?) in if error != nil { // If we get an error here from the OS, // it is possibly the callee's phone has // "Do Not Disturb" turned on; // check CXErrorCodeIncomingCallError in CXError.h self.hangupCallOnError(withId: callNotification.callId) } completion(error) } } } } private func hangupCallOnError(withId callId: String) { guard let call = callRegistry.sinchCall(forCallId: callId) else { os_log("Unable to find sinch call for callId: %{public}@", log: customLog, type: .error, callId) return } call.hangup() callRegistry.removeSinchCall(withId: callId) } ``` Note that in order to properly react to the user tapping "Answer" button on CallKit UI, we must implement one more `CXProviderDelegate` method: *SinchClientMediator+CXProviderDelegate.swift* ```swift func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { guard sinchClient != nil else { os_log("SinchClient not assigned when CXAnswerCallAction. Failing action", log: customLog, type: .error) action.fail() return } // Fetch SinchCall from callRegistry guard let call = callRegistry.sinchCall(forCallKitUUID: action.callUUID) else { action.fail() return } os_log("provider perform action: CXAnswerCallAction: %{public}@", log: customLog, call.callId) sinchClient!.audioController.configureAudioSessionForCallKitCall() call.answer() action.fulfill() } ``` ## Outgoing call UI To react to the creation of a `SinchCall` object, after receiving a VoIP notification, `SinchClientMediator` has to act as a delegate of `SinchCallClient`. *SinchClientMediator.swift* ```swift func create(withUserId userId:String, andCallback callback:@escaping (_ error: Error?) -> Void) { ... sinchClient?.callClient.delegate = self sinchClient?.start() } ``` So `SinchClientMediator` has to react to incoming call by: - assigning the call delegate - adding the incoming Sinch call to `CallRegistry`, so that the call can be fetched during interaction with CallKit UI - propagating the event to the ViewControllers which handle the presentation layer, via `SinchClientMediator` delegate methods. In this implementation, AppDelegate acts as a delegate, and is passed as a parameter via constructor. *SinchClientMediator+SinchCallClientDelegate.swift* ```swift extension SinchClientMediator: SinchCallClientDelegate { func client(_ client: SinchRTC.SinchCallClient, didReceiveIncomingCall call: SinchRTC.SinchCall) { os_log( "didReceiveIncomingCall with callId: %{public}@, from:%{public}@", log: customLog, call.callId, call.remoteUserId) os_log("app state:%{public}d", UIApplication.shared.applicationState.rawValue) call.delegate = self // We save the call object so we can either accept or deny it later when // user interacts with CallKit UI. callRegistry.addSinchCall(call) if UIApplication.shared.applicationState != .background { delegate.handleIncomingCall(call) } } } ``` Assign the delegate via constructor parameter: *SinchClientMediator.swift* ```swift // New SinchClientMediatorDelegate protocol SinchClientMediatorDelegate: AnyObject { func handleIncomingCall(_ call: SinchCall) } // Assigning the delegate via constructor parameter class SinchClientMediator : NSObject { ... weak var delegate: SinchClientMediatorDelegate! ... init(delegate: SinchClientMediatorDelegate) { ... self.delegate = delegate ... } ``` In delegate method, navigate to `CallViewController`: *AppDelegate.swift* ```swift extension AppDelegate: SinchClientMediatorDelegate { func handleIncomingCall(_ call: SinchCall) { transitionToCallView(call) } private func transitionToCallView(_ call: SinchCall) { // Find MainViewController and present CallViewController from it. let sceneDelegate = UIApplication.shared.connectedScenes .first!.delegate as! SceneDelegate var top = sceneDelegate.window!.rootViewController! while top.presentedViewController != nil { top = top.presentedViewController! } let sBoard = UIStoryboard(name: "Main", bundle: nil) guard let callVC = sBoard.instantiateViewController( withIdentifier: "CallViewController") as? CallViewController else { preconditionFailure("Error CallViewController is expected") } top.present(callVC, animated: true) } } ``` ## Ending a call Now that it's finally possible to place and receive calls between two clients, we must provide a way to terminate the call. Add `SinchClientMediator.end(call:)` method which requests CallKit the termination of the ongoing call: *SinchClientMediator.swift* ```swift func end(call: SinchCall) { guard let uuid = callRegistry.callKitUUID(forSinchId: call.callId) else { return } let endCallAction = CXEndCallAction(call: uuid) let transaction = CXTransaction() transaction.addAction(endCallAction) callController.request(transaction, completion: { error in if let err = error { os_log("Error requesting end call transaction: %{public}@", log: self.customLog, type: .error, err.localizedDescription) } self.callStartedCallback = nil }) } ``` And then implement the actual hangup action in corresponding `CXProviderDelegate` callback: *SinchClientMediator+CXProviderDelegate.swift* ```swift func provider(_ provider: CXProvider, perform action: CXEndCallAction) { guard client != nil else { os_log("SinchClient not assigned when CXEndCallAction. Failing action", log: customLog, type: .error) action.fail() return } guard let call = callRegistry.sinchCall(forCallKitUUID: action.callUUID) else { action.fail() return } os_log("provider perform action: CXEndCallAction: %{public}@", log: customLog, call.callId) call.hangup() action.fulfill() } ``` Using Connection Inspector View, implement `SinchClientMediator.end(call:)` as a reaction to tapping the "Hangup" button in CallViewController. Note that CallViewController has to access the `SinchCall` object corresponding to the ongoing call; add a `SinchCall` property to CallViewController, and make sure to set it in both paths that lead to CallViewController: *CallViewController.swift* ```swift class CallViewController: UIViewController { var call: SinchCall? ... ``` *MainViewController.swift* ```swift @IBAction func CallButtonPressed(_ sender: UIButton) { sinchClientMediator.call(userId: recipientName.text!) { (result: Result) in if case let .success(call) = result { // set the property in CallViewController callVC.call = call self.present(callVC, animated: true, completion: nil) ... ``` *AppDelegate.swift* ```swift private func transitionToCallView(_ call: SinchCall) { ... callVC.call = call top.present(callVC, animated: true) } } ``` ## Logging out A user can decide to log out, to stop receiving push notifications, or deallocate `SinchClient` for better memory efficiency. Add a new method `SinchClientMediator.logout(withCompletion:)`: *SinchClientMediator.swift* ```swift func logout(withCompletion completion: () -> Void) { defer { completion() } guard let client = sinchClient else { return } if client.isStarted { // Remove push registration from Sinch backend client.unregisterPushNotificationDeviceToken() client.terminateGracefully() } sinchClient = nil } ``` and, using Connection Inspector View, add the following method as a reaction to the user tapping the "Logout" button in MainViewController: *MainViewController.swift* ```swift @IBAction func LogoutButtonPressed(_ sender: Any) { sinchClientMediator.logout(withCompletion: { dismiss(animated: true) }) } ``` ## Next steps Now that you've built a simple app to make and receive calls, learn more about the [iOS SDK](/docs/in-app-calling/ios-cloud).