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.
- Xcode available here.
- In‑app Calling SDK for iOS.
- APNs signing key from your Apple Developer Account uploaded to your Sinch Developer Account.
In this guide, we're using the Swift SDK, but the Objective-C SDK is also available.
- Swift reference application on GitHub.
- Sample applications are bundled with the SDK package.
- Additional iOS documentation
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.
- Start by creating a new iOS app using Xcode. Select Swift as the language for your app.
- Download the Sinch Swift SDK from the SDK download page and decompress it. Add the
SinchRTC.xcframeworkfolder to your project and make sure it is set to Embed & Sign, otherwise you'll experiencedylibloading failures. - You can use the
ringback.wavandringtone.wavaudio 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. - In your project's
Target, go to theSigning & Capabilitiestab and enable "Voice over IP" underBackground Modesto integrateCallKit/LiveCommunicationKit. Calls throughCallKit/LiveCommunicationKitwill fail if you skip this step. - While in your project's
Target→Signing & Capabilities, add the "Push Notifications" capability. Without this capability, the app can't acquire a device token or receive notifications.
Using the Xcode storyboard, create three view controllers named as follows:
LoginViewController.swift— view controller for creating a client and logging in a userMainViewController.swift— view controller from which calls will be initiated and receivedAudioCallViewController.swift— view controller with calling actions and information
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
}
}
}Debug logs can be verbose and may contain misleading messages. Enable logging only when investigating specific errors or behavior related to Sinch components.
In order to place calls between two users, you must register the users on the Sinch backend.
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.
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.
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
}
}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.
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)
}
}Now that your application is created, you can configure that application to make and receive a call with CallKit / LiveCommunicationKit.