Skip to content
Last updated

Getting started with the Sinch In‑app Calling iOS SDK

This guide shows you how to get started integrating your iOS application with the In‑app Calling iOS SDK. Because we're just getting started, this guide covers how to create a Sinch client and how to make and receive calls with either CallKit or LiveCommunicationKit.

Prerequisites

Note:

In this guide, we're using the Swift SDK, but the Objective-C SDK is also available.

Reference applications and additional files

  • Swift reference application on GitHub.
  • Sample applications are bundled with the SDK package.
  • Additional iOS documentation

Upload your APNs signing keys

When a call is placed from UserA to UserB, the Sinch backend delivers a VoIP push notification via APNs to UserB in order to initiate the call. To allow Sinch to send push notifications on your behalf, you must upload your APNs signing key to your Sinch application configuration. You can find instructions on how to generate and upload your APNs signing key here.

Setup the Xcode project

  1. Start by creating a new iOS app using Xcode. Select Swift as the language for your app.
  2. Download the Sinch Swift SDK from the SDK download page and decompress it. Add the SinchRTC.xcframework folder to your project and make sure it is set to Embed & Sign, otherwise you'll experience dylib loading failures.
  3. You can use the ringback.wav and ringtone.wav audio files from the decompressed SDK folder (in the sample application), from the reference applications on GitHub, or provide your own audio files and add them to your app bundle.
  4. In your project's Target, go to the Signing & Capabilities tab and enable "Voice over IP" under Background Modes to integrate CallKit / LiveCommunicationKit. Calls through CallKit / LiveCommunicationKit will fail if you skip this step.
  5. While in your project's TargetSigning & Capabilities, add the "Push Notifications" capability. Without this capability, the app can't acquire a device token or receive notifications.

Create the app views

Using the Xcode storyboard, create three view controllers named as follows:

  • LoginViewController.swift — view controller for creating a client and logging in a user
  • MainViewController.swift — view controller from which calls will be initiated and received
  • AudioCallViewController.swift — view controller with calling actions and information

Enable Sinch logs

When debugging, it can be useful to set a log callback to enable Sinch logging and make it easier to understand potential problems. You can do that by invoking SinchRTC.setLogCallback(). The example below extends Sinch LogSeverity to map Sinch log severity to OSLogType:

AppDelegate.swift

import OSLog
import SinchRTC

class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions:
                           [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Sinch Logging setup.
    SinchRTC.setLogCallback { (severity: SinchRTC.LogSeverity, 
                                   area: String, 
                                    msg: String, 
                                      _: Date) in
      os_log("%{public}@", log: .sinchOSLog(for: area), type: severity.osLogType, msg)
    }

    return true
  }
}

// Map Sinch log severity to OSLogType.
extension LogSeverity {

  var osLogType: OSLogType {
    switch self {
      case .info, .warning: return .default
      case .critical: return .fault
      case .trace: return .debug
      default: return .default
    }
  }
}
Note:

Debug logs can be verbose and may contain misleading messages. Enable logging only when investigating specific errors or behavior related to Sinch components.

User registration

In order to place calls between two users, you must register the users on the Sinch backend.

SinchClientMediator

First, create a Config.swift file to store the credentials of your Sinch app from your dashboard. These are used to authenticate your Sinch client to the Sinch backend.

Config.swift

let APPLICATION_KEY = "..."

// Developers must not, under any circumstances, commit APPLICATION_SECRET in client code.
// This introduces a serious security risk.
let APPLICATION_SECRET = "..."

let ENVIRONMENT_HOST = "ocra.api.sinch.com"

Create a SinchClientMediator class. This class will act as:

  • a wrapper around the root Sinch component, SinchClient
  • the delegate of SinchClient

In particular, SinchClientMediator owns a SinchClient object, and implements createAndStartClient(with userId: String, and callback: @escaping (_ error: Error?) -> Void), which creates and starts its Sinch client.

Note:

The completion callback is stored in a property, so that it can be accessed later in the SinchClient delegate callback.

In this example app, the instance of SinchClientMediator is owned by AppDelegate, and initialized in AppDelegate.application(didFinishLaunchingWithOptions:). Make sure the SinchClientMediator variable is public, so that ViewControllers can access it later.

SinchClientMediator.swift


typealias ClientStartedCallback = (_ error: Error?) -> Void

class SinchClientMediator {

  var clientStartedCallback: ClientStartedCallback!
  
  private(set) var sinchClient: SinchClient?
  
  // Creating and starting a client for particular user.
  // https://developers.sinch.com/docs/in-app-calling/ios/sinch-client/#creating-the-sinclient
  func createAndStartClient(with userId: String, and callback: @escaping (_ error: Error?) -> Void) {
    do {
      sinchClient = try SinchRTC.client(withApplicationKey: APPLICATION_KEY,
                                        environmentHost: ENVIRONMENT_HOST,
                                        userId: userId)
    } catch let error as NSError {
      os_log("Failed to create sinchClient",
             log: .sinchOSLog(for: SinchClientMediator.identifier),
             type: .info,
             error.localizedDescription)

      callback(error)
    }

    clientStartedCallback = callback

    guard let sinchClient = sinchClient else { return }

    sinchClient.delegate = self
    sinchClient.start()
  }
}

Now, create an extension of SinchClientMediator to conform to the SinchClientDelegate protocol, implementing credential provisioning and monitoring of the Sinch client status.

Note:

The stored copy of clientStartedCallback is used and reset in clientDidStart() and clientDidFail().

SinchClientMediator+SinchClientDelegate.swift

// Extension to implement credential provisioning and monitoring of Sinch client status.
extension SinchClientMediator: SinchClientDelegate {

  // Client authorizing with JWT.
  // https://developers.sinch.com/docs/in-app-calling/ios/sinch-client/#authorizing-the-client
  func clientRequiresRegistrationCredentials(_ client: SinchRTC.SinchClient,
                                             withCallback callback: SinchRTC.SinchClientRegistration) {
    do {
      // WARNING: test implementation to create JWT token, shouldn't be done for production application.
      let jwt = try SinchJWT.sinchJWTForUserRegistration(withApplicationKey: APPLICATION_KEY,
                                                         applicationSecret: APPLICATION_SECRET,
                                                         userId: client.userId)

      callback.register(withJWT: jwt)
    } catch {
      callback.registerDidFail(error: error)
    }
  }

  func clientDidStart(_ client: SinchClient) {
    guard clientStartedCallback != nil else { return }

    clientStartedCallback(nil)
    clientStartedCallback = nil
  }

  func clientDidFail(_ client: SinchClient, error: Error) {
    guard clientStartedCallback != nil else { return }

    clientStartedCallback(error)
    clientStartedCallback = nil
  }
}
Note:

To simplify this tutorial, the JWT token required to authenticate the Sinch client (see SinchClient docs) is generated in the client app. This is bad security practice, as it stores the application secret in client code. In your application, JWT generation must be delegated to your backend. For an example implementation of JWT user registration, refer to the SinchJWT.swift file in Sinch's Swift reference application or in the sample application bundled with the Swift SDK.

User login

Add a property of type SinchClientMediator to LoginViewController, and assign it from the SinchClientMediator member of AppDelegate.

LoginViewController.swift

final class LoginViewController: UIViewController {

  // From AppDelegate pass clientMediator to LoginViewController.
  var clientMediator: SinchClientMediator?
}

Then, using the Connections Inspector, implement SinchClient instantiation and user registration in response to tapping the "Login" button in LoginViewController. If the Sinch client is created and started successfully, transition to MainViewController.

LoginViewController.swift

final class LoginViewController: UIViewController {

  ...

  @IBAction func login(_ sender: UIButton) {
    // Assume `username` is read from a text field.
    clientMediator?.createAndStartClient(with: username) { [weak self] error in
      guard let self = self else { return }
      
      if let error = error {
        os_log("SinchClient started with error: %{public}@",
          log: .sinchOSLog(for: LoginViewController.identifier),
          type: .error,
          error.localizedDescription)
      }
      
      presentMainViewController()
    }
  }
  
  private func presentMainViewController() {
    // prepareViewController(identifier: "main") is a UIViewController extension.
    // that instantiates a view controller from the storyboard.
    let mainViewController: MainViewController? = prepareViewController(identifier: "main")

    guard let mainViewController = mainViewController else {
      preconditionFailure("Error MainViewController is expected")
    }

    mainViewController.clientMediator = clientMediator
    
    os_log("Preparing MainViewController for presentation", log: .sinchOSLog(for: LoginViewController.identifier))

    present(mainViewController, animated: true)
  }
}

Next steps

Now that your application is created, you can configure that application to make and receive a call with CallKit / LiveCommunicationKit.