# Making an audio call This guide shows you to how to make an audio call in your iOS app. We assume you've already set up your iOS app with the In-app Calling iOS SDK. If you haven't already, [create an app](/docs/in-app-calling/getting-started/ios/create-app) first. Note: Starting from iOS13, both incoming and outgoing calls should be reported and handled via CallKit. For further reading about the intentions and usage of CallKit integration, refer to [Push notifications documentation](https://developers.sinch.com/docs/in-app-calling/ios/push-notifications-callkit/#callkit). ## Report an outgoing call to CallKit First, let's add a few properties to `SinchClientMediator`: - a `CXCallController` object and a `CXProvider` object, which will be used to request the creation of new CallKit calls - a property to store the call creation callback - a `CallRegistry` object, to map Sinch's callIds to CallKit's callId. For an example implementation of `CallRegistry`, please refer to `CallRegistry.swift` file in Sinch's Swift sample app, bundled together with Swift SDK. *SinchClientMediator.swift* ```swift class SinchClientMediator : NSObject { typealias CallStartedCallback = (Result) -> Void var callStartedCallback: CallStartedCallback! let callController: CXCallController! = CXCallController() let callRegistry = CallRegistry() var provider: CXProvider! override init() { super.init() // set up CXProvider let providerConfig = CXProviderConfiguration(localizedName: "my_app") providerConfig.supportsVideo = false providerConfig.ringtoneSound = "ringtone.wav" provider = CXProvider(configuration: providerConfig) provider.setDelegate(self, queue: nil) } ``` Next, add `SinchClientMediator.call(userId:withCallback:)` method, which requests the initiation of a new CallKit call. Note: At this point the Sinch client is still not involved. *SinchClientMediator.swift* ```swift func call(userId destination:String, withCallback callback: @escaping CallStartedCallback) { let handle = CXHandle(type: .generic, value: destination) let initiateCallAction = CXStartCallAction(call: UUID(), handle: handle) initiateCallAction.isVideo = false let initOutgoingCall = CXTransaction(action: initiateCallAction) // store for later use callStartedCallback = callback callController.request(initOutgoingCall, completion: { error in if let err = error { os_log("Error requesting start call transaction: %{public}@", log: self.customLog, type: .error, err.localizedDescription) DispatchQueue.main.async { self.callStartedCallback(.failure(err)) self.callStartedCallback = nil } } }) } ``` This way, CallKit events will be handled by `SinchClientMediator` by implementing the callbacks of `CXProviderDelegate` protocol; note that conformity to `NSObject` is required. For the time being, we're interested in implementing only a subset of the `CXProviderDelegate` methods: - notification of `AVAudioSession` events to the SinchClient: *SinchClientMediator+CXProviderDelegate.swift* ```swift func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { guard sinchClient != nil else { return } sinchClient!.callClient.provider(provider: provider, didActivateAudioSession: audioSession) } func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { guard sinchClient != nil else { return } sinchClient!.callClient.provider(provider: provider, didDeactivateAudioSession: audioSession) } ``` - starting a Sinch call as `CXStartCallAction` callback is invoked, by accessing `SinchCallClient` subcomponent which is the entry point of calling functionalities. If the call started successfully, the callId is stored in `CallRegistry` and `SinchClientMediator` is assigned as delegate of the call. *SinchClientMediator+CXProviderDelegate.swift* ```swift func provider(_ provider: CXProvider, perform action: CXStartCallAction) { defer { callStartedCallback = nil } guard sinchClient != nil else { action.fail() callStartedCallback?(.failure(Errors.clientNotStarted( "SinchClient not assigned when CXStartCallAction."))) return } // actual start of Sinch call let callResult = sinchClient!.callClient.callUser(withId: action.handle.value) switch callResult { case let .success(call): callRegistry.addSinchCall(call) callRegistry.map(callKitId: action.callUUID, toSinchCallId: call.callId) // Assigning the delegate of the newly created SinchCall. call.delegate = self action.fulfill() case let .failure(error): os_log("Unable to make a call: %s", log: customLog, type: .error, error.localizedDescription) action.fail() } callStartedCallback?(callResult) } ``` ## Outgoing call UI In this app, `SinchClientMediator` is the only delegate of `SinchCall`s and takes care of CallKit integration, but it also forwards events to CallViewController which controls call-related UI events. In this implementation this is accomplished by implementing an Observer pattern, where CallViewController is the observer of `SinchClientMediator`. *SinchClientMediator.swift* ```swift class SinchClientMediator : NSObject { var observers: [Observation] = [] ... private func fanoutDelegateCall(_ callback: ( _ observer: SinchClientMediatorObserver) -> Void) { // Remove dangling before calling observers.removeAll(where: { $0.observer == nil }) observers.forEach { callback($0.observer!) } } class Observation { init(_ observer: SinchClientMediatorObserver) { self.observer = observer } weak var observer: SinchClientMediatorObserver? } func addObserver(_ observer: SinchClientMediatorObserver) { guard observers.firstIndex(where: { $0.observer === observer }) != nil else { observers.append(Observation(observer)) return } } func removeObserver(_ observer: SinchClientMediatorObserver) { if let idx = observers.firstIndex(where: { $0.observer === observer }) { observers.remove(at: idx) } } ``` And now make sure that CallViewController is added as an observer as soon as the view is loaded, and extend it to conform to `SinchClientMediatorObserver` observer. *CallViewController.swift* ```swift class CallViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let appDelegate = UIApplication.shared.delegate as! AppDelegate sinchClientMediator = appDelegate.sinchClientMediator sinchClientMediator.addObserver(self) } } extension CallViewController: SinchClientMediatorObserver { func callDidProgress(_ call: SinchCall) { playSoundFile("ringback.wav") } func callDidEstablish(_ call: SinchCall) { sinchClientMediator.sinchClient?.audioController.stopPlayingSoundFile() } func callDidEnd(_ call: SinchCall) { dismiss(animated: true) sinchClientMediator.sinchClient?.audioController.stopPlayingSoundFile() sinchClientMediator.removeObserver(self) } } ``` Finally, using the Connection Inspector view, trigger `SinchClientDelegate.call(userId:withCallback:)` as a reaction to pushing the "Call" button in MainViewController, and present CallViewController in case of success. *MainViewController.swift* ```swift @IBAction func CallButtonPressed(_ sender: UIButton) { sinchClientMediator.call(userId: recipientName.text!) { ( result: Result) in if case let .success(call) = result { let sBoard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil) guard let callVC = sBoard.instantiateViewController( withIdentifier: "CallViewController") as? CallViewController else { preconditionFailure("Error: CallViewController is expected") } self.present(callVC, animated: true, completion: nil) } else if case let .failure(error) = result { os_log("Call failed failed: %{public}@", log: self.customLog, type: .error, error.localizedDescription) } } } ``` Note: At this point you still can't receive calls; to test your implementation up to this point, you can try to place a call to a non-existing user and verify that the call fails with an error message along the lines of: *"Unable to connect call (destination user not found)"*. ## Next steps Now that you've made a call, you can set up your application to handle incoming calls. Make a call