Home
/
Calls
/
iOS

Calls integration to Chat

You can integrate Sendbird Calls to Sendbird Chat to provide users with a seamless experience in using Calls and Chat services by allowing them to make a call within a chat channel. With the implementation of Calls integration to Chat, the call screen will appear when the call starts and when the call ends users will return to the chat view.

Showing Calls integration to Chat view

Note: To turn on Calls integration to Chat on the Sendbird Dashboard, go to Settings > Chat > Messages.


Benefits

Calls integration to Chat provides the following benefits:

Natively integrated service

Sendbird Calls and Sendbird Chat are provided from the same app to offer an advanced in-app chat experience for users.

Call within channel

Users can directly make a call to anyone in a channel without leaving the app.

Immersive user experience

Smooth transition between Calls and Chat within a chat channel will make the user experience more engaging.


How it works

Since Calls integration to Chat is a way to add call capabilities to Sendbird Chat, it requires an app that uses Chat. If you already have one, you are ready to move to the next step. If you don’t have an app, learn how to set up Sendbird Chat from our About Chat SDK page.


Prerequisites

To use Calls integration to Chat, an environment setup is first needed for both Sendbird Calls and Sendbird Chat using the SDKs. To learn more about each, refer to Sendbird Calls for Quickstart and Sendbird Chat samples.


Requirements

The minimum requirements for Calls integration to Chat are:

  • iOS 9.0 or later
  • Installation of Git Large File Storage
  • SendBirdWebRTC
  • Sendbird Chat sample
  • Sendbird Chat SDK
  • Sendbird Calls SDK

Install the SDKs

To install the Calls SDK and the Chat SDK, do the following steps:

Step 1: Configuration

Sendbird Calls SDK and Chat SDK both use singleton interfaces. Since these two SDKs work independently, create appropriate functions that can handle them together.

Step 2: Initialize the Calls SDK and the Chat SDK

Find your application ID from the Sendbird Dashboard to use in the Calls SDK and the Chat SDK.

Light Color Skin
Copy
// SendBirdHelper.swift
// Initialize an instance for each of SendBirdCall and SBDMain to use APIs in your app.
static public func configure(_ appID: String) {
    SendBirdCall.configure(appId: APP_ID)
    SBDMain.initWithApplicationId(APP_ID)
}

Step 3: Log in to the Calls SDK and the Chat SDK

To log in to the Calls and Chat SDKs, create a function that allows you to authenticate the Calls SDK and the Chat SDK together.

Light Color Skin
Copy
// SendBirdHelper.swift
// The user ID should be unique to your Sendbird application.
static public func login(userId: String, nickname: String, completionHandler: ((_ user: SendBirdUser?, _ error: Error?) -> Void)?) {
    var loginError: Error?
    var chatUser: SBDUser?
    var callUser: User?

    let group = DispatchGroup()

    group.enter()
    SendBirdCall.authenticate(with: AuthenticateParams(userId: userId)) { (user, error) in
        defer { group.leave() }

        guard error == nil else {
            loginError = error
            return
        }

        callUser = user
    }

    group.enter()
    SBDMain.connect(withUserId: userId accessToken: nil) { (user, error) in
        defer { group.leave() }

        guard error == nil else {
            loginError = error
            return
        }

        chatUser = user
    }

    // A function to authenticate the user to the Calls and Chat SDKs has been created successfully.
    ...

    group.notify(queue: executionQueue) {
        guard let user = SendBirdUser(chatUser: chatUser, callUser: callUser) else {
            completionHandler?(nil, loginError)
            return
        }

        completionHandler?(user, nil)
        // The user has been successfully authenticated using this function.
    }
}

As written above, the SendBirdCall.authenticate() method and SBDMain.connect() method are used to authenticate the current user to use Calls and Chat APIs in your app.

Step 4: Log out from the Calls SDK and the Chat SDK

To log out from the Calls SDK and the Chat SDK, create a function that handles the two SDKs together like it was to log in.

Light Color Skin
Copy
// SendBirdHelper.swift
func logout(completionHandler: (() -> Void)?) {
    let group = DispatchGroup()

    group.enter()
    let deauthenticateChat: (([AnyHashable: Any]?, SBDError?) -> Void) = { _, _ in
        SBDMain.disconnect {
            group.leave()
        }
    }

    if let chatPushToken = self.chatPushToken {
        SBDMain.unregisterPushToken(chatPushToken, completionHandler: deauthenticateChat)
    } else {
        deauthenticateChat(nil, nil)
    }

    SBDMain.disconnect {
        group.leave()
    }

    group.enter()
    let deauthenticateCall: ((SBCError?) -> Void) = { _ in
        SendBirdCall.deauthenticate { (error) in
            group.leave()
        }
    }

    if let voipPushToken = self.callPushToken {
        SendBirdCall.unregisterVoIPPush(token: voipPushToken, completionHandler: deauthenticateCall)
    } else {
        deauthenticateCall(nil)
    }

    group.notify(queue: DispatchQueue.global()) {
        completionHandler?()
        //The user has been successfully deauthenticated using this function.
    }
}

Register push tokens

Push notifications services allow users to receive notifications when the app is in the background. Before utilizing this feature, you need to register appropriate push tokens to Sendbird server. To turn on this feature, go to Settings > Notifications in your dashboard.

For Calls integration to Chat, both CallKit and PushKit are used to register push tokens.

In AppDelegate.swift, save the remote device token and PushKit token from the following delegate methods:

Light Color Skin
Copy
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: DATA) {}

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {}

Then, register them to Sendbird server as shown below:

Light Color Skin
Copy
// Register the current user's push token to Sendbird server.
public func registerRemotePushToken(_ token: pushToken, completionHandler: @escaping (SBDPushTokenRegistrationStatus, Error?) -> ()) {
    SBDMain.registerDevicePushToken(pushToken, unique: false) { (result, error) in
        if result == .success {
            self.chatPushToken = pushToken
        }

        completionHandler(result, error)
    }
}

public func registerVoIPPushToken(_ token: voipPushToken, completionHandler: ((_ error: Error?) -> Void)?) {
    SendBirdCall.registerVoIPPush(token: voipPushToken) { (error) in
        if error == nil {
            self.callPushToken = voipPushToken
        }

        completionHandler?(error)

        // The VoIP push token has been registered successfully.
    }
}

Unregister push tokens

Before logging out, unregister the push tokens as shown below:

Light Color Skin
Copy
SendBirdCall.registerVoIPPush(token: TOKEN) { (error) in
    if error == nil {
        // Handle error
    }

    ...
}

SBDMain.unregisterPushToken(TOKEN) { (response, error) in
    if error == nil {
        // Handle error
    }

    // The VoIP push token has been unregistered successfully.
    ...
}

Make a call

For integration between the Calls and Chat services, the Calls SDK provides a specific option when dialing. You can provide the group channel URL to a DialParams object of Sendbird Calls as shown below:

Light Color Skin
Copy
let chatOptions = SendBirdChatOptions(channelURL: self.channel.channelUrl)
let dialParams = DialParams(calleeId: CALLEE_ID, isVideoCall: IS_VIDEO_CALL, callOptions: CallOptions(), sendbirdChatOptions: chatOptions)

SendBirdCall.dial(with: dialParams) { (call, error) in
    if error == nil {
        // Handle error
    }

    // The call has been made successfully.
    ...
}

When a group channel URL is provided to the DialParams object, messages containing call information such as calling statuses and duration will be automatically sent to the channel when the call starts and ends.

The messages will contain call information in the plugins field of the SBDBaseMessage instance which can be used to properly show information about the calls.


Create custom UI components

The sample app for Calls integration to Chat is built based on Sendbird Chat. In the Chat sample app, every chat message is associated with a specific type of instance from the UITableViewCell class. With different types of messages such as user message, file message, and admin message, different types of instances of the UITableViewCell class are shown to the user, offering a more intuitive user experience.

For UI components for Calls integration to Chat, create an instance of the UITableViewCell class, like the following, that will be shown to users when they make or receive a call:

Showing an UI component of an outgoing voice call.

An example of UI components you can create is demonstrated in these view controller files in the sample app:

  • GroupChannelOutgoingCallMessageTableViewCell.swift
  • GroupChannelOutgoingCallMessageTableViewCell.xib

To create similar UI components for Calls integration to Chat, follow the steps below.

  1. In Xcode, go to File > New > File > Cocoa Touch Class.
  2. Create a new class and name it CallMessageTableViewCell.swift. Select UITableViewCell as the subclass.
  3. Go to File > New > File, then select the View template in User Interface. Create a new xib file and name it CallMessageTableViewCell.xib.
  4. Design the UITableViewCell class in CallMessageTableViewCell.xib file. Register the custom class to the CallMessageTableViewCell class.

Register UI components

Use the UI components created to integrate Sendbird Calls to the group channel view controller in Chat. Register the UITableViewCell class to the UITableView instance of the chat view controller.

Then, add the following to the viewDidLoad() method of GroupChannelChatViewController.swift file from the Chat sample app, which will create new table view cells with the identifier CallMessageTableViewCell.

Light Color Skin
Copy
let cellNib = UINib(nibName: String(describing: CallMessageTableViewCell.self), bundle: Bundle(for: CallMessageTableViewCell.self))

self.messageTableView.register(cellNib, forCellReuseIdentifier: "CallMessageTableViewCell")

Show UI components

To show the registered UI components, you have to identify which messages are associated with the Calls SDK and show the messages by using the CallMessageTableViewCell class.

  1. The SBDBaseMessage class from Sendbird Chat SDK has a field called plugins where you can store additional information to default values. The key-value plugins are delivered as [String: Any] dictionary.
  2. When a call is made from the Calls SDK, the plugin field of a message associated with the call will contain the following information: vendor: sendbird, type: call.
  3. Then, for the messages that have these fields, show the UI component created.
  4. In the tableView() method of the GroupChannelChatViewController.swift file from the Chat sample, add the following:
Light Color Skin
Copy
// GroupChannelChatViewController.swift
if currMessage is SBDUserMessage {
    guard let userMessage = currMessage as? SBDUserMessage else { return cell }
    if userMessage.plugin["vendor"] == "sendbird",  userMessage.plugin["service"] == "call" {
        let callTableViewCell = tableView.dequeueReusableCell(withIdentifier: "CallMessageTableViewCell") as? CallMessageTableViewCell

        ...
    }
}

Extract call data from plugins

A model shown below demonstrates a way to extract specific information from the plugin about Sendbird Calls.

Light Color Skin
Copy
@objc
class CallInfo: NSObject, Decodable {
    let callId: String
    let callType: String

    let isVideoCall: Bool

    let duration: Int64?
    let endResult: DirectCallEndResult?

    init?(from dictionary: [String: Any]) {
        guard let callId = dictionary["call_id"] as? String else { return nil }
        guard let callType = dictionary["call_type"] as? String else { return nil }
        guard let isVideoCall = dictionary["is_video_call"] as? Bool else { return nil }

        self.callId = callId
        self.callType = callType
        self.isVideoCall = isVideoCall

        self.duration = dictionary["duration"] as? Int64
        if let endResult = dictionary["end_result"] as? String {
            self.endResult = DirectCallEndResult(rawValue: endResult)
        } else {
            self.endResult = nil
        }
    }
}

Use the model to retrieve information about the call as shown below:

Light Color Skin
Copy
// GroupChannelChatViewController.swift
guard let callDetail = userMessage.plugin["detail"] as? [String: Any] else { return }
guard let callInfo = CallInfo(from: callDetail) else { return }

Using the detailed information from the message plugin, fill the CallMessageTableViewCell.

Light Color Skin
Copy
// GroupChannelChatViewController.swift
if callInfo.isVideoCall {
    callTableViewCell.callTypeImageView.image = UIImage(systemName: "video.fill")
} else {
    callTableViewCell.callTypeImageView.image = UIImage(systemName: "phone.fill")
}

if let duration = callInfo.duration, let reason = callInfo.endReason {
    switch reason {
        case .completed:
            callTableViewCell.textMessageLabel.text = "\(duration.timerText())"
        case .unknown, .none:
            callTableViewCell.textMessageLabel.text = "Unknown Error"
        default:
            callTableViewCell.textMessageLabel.text = reason.capitalized()
    }
} else {
    callTableViewCell.textMessageLabel.text = "\(callInfo.isVideoCall ? "Video" : "Voice") calling..."
}

The above model shows how to extract the information from the plugin to add them to the callTableViewCell.


Create view controller

The call view controller provides interfaces for actions such as ending a call, muting or unmuting , or offers local and remote video views on a user's screen.

To implement a voice call interface to your app, refer to VoiceCallViewController.swift and to implement a video call interface, refer to VideoCallViewController.swift on our Sendbird Calls for Quickstart. Once both interfaces are implemented, the two will be referred to as CallingViewController.


Use message bubble to call

Calls integration to Chat can provide a seamless user experience by allowing users to initiate a new call directly from a channel by tapping the messages that contain call information.

First, add an UITapGestureRecognizer instance to the CallMessageCell instance:

Light Color Skin
Copy
let clickMessageContainerGesture = UITapGestureRecognizer(target: self, action: #selector(didClickCallMessage(_:)))
callTableViewCell.contentView.addGestureRecognizer(clickMessageContainerGesture)

@objc func didClickCallMessage(_ gesture: UITapGestureRecognizer) { }

If the view controller is hidden during a call, tap the message which corresponds to the call to show the view controller on the screen.

Light Color Skin
Copy
guard let call = SendBirdCall.getCall(forCallId: callInfo.callId), call.isOngoing else { return }
let callingViewController = CallingViewController(nibName: "CallingViewController", bundle: nil)
callingViewController.call = call

self.present(callingViewController, animated: true, completion: nil)

If you would like to make a call again, tap any one of the status messages and a pop-up will appear for voice or video call options.

Create an UIAlertController instance and add audioCallAction and videoCallAction as action items for initiating a new voice or video call. The view controller then will appear for the new call.

Light Color Skin
Copy
let alertController = UIAlertController(title: "Call \(remoteId)", message: nil, preferredStyle: .actionSheet)
let audioCallAction = UIAlertAction(title: "Audio Call", style: .default) { _ in
    self.dial(userId: remoteId, isVideoCall: false)
}
let videoCallAction = UIAlertAction(title: "Video Call", style: .default) { _ in
    self.dial(userId: remoteId, isVideoCall: true)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in
    alertController.dismiss(animated: true, completion: nil)
}

alertController.addAction(audioCallAction)
alertController.addAction(videoCallAction)
alertController.addAction(cancelAction)

self.present(alertController, animated: true, completion: nil)
...

func dial(userId: String, isVideoCall: Bool) {
    let options = SendBirdChatOptions(channelURL: self.channel!.channelUrl)
    SendBirdCall.dial(with: DialParams(calleeId: userId, isVideoCall: isVideoCall, callOptions: CallOptions(), sendbirdChatOptions: options)) { (call, error) in
        guard let call = call else { return }
        guard error == nil else { return }
        DispatchQueue.main.async {
            let callingViewController = CallingViewController(nibName: "CallingViewController", bundle: nil)
            callingViewController.call = call

            self.present(callingViewController, animated: true, completion: nil)
        }
    }
}

Other requirements

To enhance the user experience for Calls integration to Chat, features such as receiving calls using Callkit and push notifications should be added. Refer to Calls integration to Chat for Quickstart and learn more about these essential features.