Chat About Typing Status

About Typing Status: I have added below delegate into controller in which I have added “SBUChannelViewController” as child controller.

// MARK: - SBUMessageInputViewDelegate

extension SBParentChatVC: SBUMessageInputViewDelegate {

 func messageInputViewDidStartTyping() {
    //self.channelViewModel?.startTypingMessage()
    self.selectedChannelInfo?.startTyping()
}

 func messageInputViewDidEndTyping() {
    //self.channelViewModel?.endTypingMessage()
    self.selectedChannelInfo?.endTyping()
}

}

But it is not displaying typing status to the opposite user. Additionally, when an Android user is typing, it is showing status to us. So, are we missing something to send event?

Hello @Ibrahim_Malada,

If SBParentChatVC is SBUChannelViewController's child controller, wasn’t it a problem to declare SBUMessageInputViewDelegate in extension?
Your code should have a Redundant performance error.
The startTypeMessage() / endTypeMessage() functions in viewModel must be called to send the typing status to the other party.
Your code blocks these functions so the typing status is not delivered to the opposite user.
Override the SBUMessageInputViewDelegate functions and call the Super functions.
Please refer to the code below.

class SBParentChatVC: SBUChannelViewController {
    // ...
}


// MARK: - SBUMessageInputViewDelegate
extension SBParentChatVC {
    override func messageInputViewDidStartTyping() {
        super.messageInputViewDidStartTyping()
        self.selectedChannelInfo?.startTyping()
    }
    
    override func messageInputViewDidEndTyping() {
        super.messageInputViewDidEndTyping()
        self.selectedChannelInfo?.endTyping()
    }
}

Hello @Tez

Thanks for your reply. But basically I cannot do like below:

class SBParentChatVC: SBUChannelViewController {
    // ...
}

Reason is that, I have other events as well as other objects used into main controller.
So, if I will do
class SBParentChatVC: SBUChannelViewController
Intead of
class SBParentChatVC: UIViewController

It is giving other errors related to:
@IBAction func btnClicked(_ sender: UIButton)

And below delegate method:
extension SBParentChatVC: SBDChannelDelegate {
func channelDidUpdateTypingStatus(_ sender: SBDGroupChannel) {
}
}

Which I am using for updating some local controls.

Can you please let me know for above?

Thank you.

Hello @Ibrahim_Malada,
SBUMessageInputView works seamlessly under SBUChannelViewController.
If you manage channel (SBDGroupChannel) objects directly, you can utilize the following two functions.

  • channel.startTyping()
  • channel.endTyping()

by the way, why don’t you use “SBUChannelViewController” and only use “SBUMessageInputViewDelegate”?
If it’s because there’s a problem, I’ll consider making improvements in the future. :pray:

Hi @Tez

Thanks for your feedback. Basically, this is what I am doing.

In my initial question post: “self.selectedChannelInfo” is the object of “SBDGroupChannel”. But it is not sending the typingStart and End status to other user.

extension SBParentChatVC: SBUMessageInputViewDelegate {
 func messageInputViewDidStartTyping() {
    //self.channelViewModel?.startTypingMessage()
    self.selectedChannelInfo?.startTyping()
}

 func messageInputViewDidEndTyping() {
    //self.channelViewModel?.endTypingMessage()
    self.selectedChannelInfo?.endTyping()
}
}

Can you help with this ?

Appreciate your answers.

@Ibrahim_Malada
The problem is iOS apps implemented in the same code shown the typing status of Android users, but not shown the typing status of iOS users. right?

Are the functions(messageInputViewDidStartTyping, messageInputViewDidEndTyping) of SBUMessageInputViewDelegate being called?

@Tez

No, these delegate methods are not calling.

at viewDidLoad, I have added below line:
SBDMain.add(self as SBDChannelDelegate, identifier: self.description)

This is because I can get the typingStatus of other users like below:

extension SBParentChatVC: SBDChannelDelegate {
func channelDidUpdateTypingStatus(_ sender: SBDGroupChannel) {
}
}

But “messageInputViewDidStartTyping” and “messageInputViewDidEndTyping” are not calling.

@Ibrahim_Malada

SBUMessageInputViewDelegate is Sendbird’s UIKit component.
In order to perform the action you want, you must set the delegate setting on the SBUMessageInputView type object.

class SBParentChatVC: ... {
    var messageInputView = SBUMessageInputView()

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        SBDMain.add(self as SBDChannelDelegate, identifier: self.description)
        self.messageInputView.delegate = self // <-----
        ...
    }

}


If you want to use ChatSDK only, startTyping(), endTyping() functions should be called directly whenever a status change is required.

I don’t know if you want to use ChatSDK only or UIKit together.

Yes, @Tez

But how can I set the child controller delegate to main “SBParentChatVC”.

As I mentioned, I have added “SBUChannelViewController” controller as a child controller. So, “messageInputView” will be there in Chat Controller. But it’s not calling automatically.

That’s why I have tried to put that delegate as a sub.

Additionally, I have tried with opening the default demo controller as below.

//Sendbird
let mainVC = SBUChannelListViewController()
let naviVC = UINavigationController(rootViewController: mainVC)
naviVC.modalPresentationStyle = .fullScreen
//naviVC.isToolbarHidden = true
//naviVC.isNavigationBarHidden = true
present(naviVC, animated: true)
//Sendbird

In that, when I am typing then also it is not sending the typingStart and End to opposite user.

@Ibrahim_Malada
Does SBParentChatVC have SBUChannelViewController object and other ViewController objects as child?
And you want to handle the SBUMessageInputViewDelegate functions of SBUChannelViewController, in your SBParentChatVC class.
Am I right to understand?

Yes, your understanding is right.

Currently, “SBParentChatVC” don’t have an other sub-controller except " SBUChannelViewController" as mentioned. It is added as child controller.

And please note that, it is added as below:

let channelVC = SBUChannelViewController(
            channelUrl: channelUrl,
            messageListParams: messageListParams
            
        )
        channelVC.navigationController?.isToolbarHidden = true
        channelVC.navigationController?.isNavigationBarHidden = true
        
        self.addChild(channelVC)

@Ibrahim_Malada,

In SBParentChatVC, the child is SBUChannelViewController, but the delegate implementation of SBUChannelViewController cannot be controlled in the extension of SBParentChatVC.

Let’s check the log to see if typing status cannot be sent.

  • Please add the below code to show UIKit logs in your AppDelegate.
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
                 //...
                 SBUMain.setLogLevel(.all)
                 //...
}
  • Then run the app and input a message on the channel.
    • Did update typing status

If this log is displayed when the input state changes, it is being sent properly.
But if you don’t see this log, could you share the implementation of SBParentChatVC?

Hello @Tez

I have put the log as per your suggestion. But typing related log is not showing when I am typing to send messages.

                 SBUMain.setLogLevel(.all)

I cannot attach the file, so I am posting the implementation of " SBParentChatVC" controller over here. Please review and let me know.

============================================================

import UIKit
import SendBirdUIKit
import CallKit // sendBird calls
import SendBirdCalls // sendBird calls

class SBParentChatVC: UIViewController { //UIViewController //SBUChannelViewController
//MARK: - Variable Declaration

//For Conversations //For Provider
var IsCallingContinue : Bool = false

var channelUrl : String = ""
var selectedChannelInfo : SBDGroupChannel?

//MARK: - ViewController Life Cycle
override func viewDidLoad() {
    super.viewDidLoad()
    self.setUpUI()
}
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    APP_DELEGATE.currentVC = self
    //APP_DELEGATE.chatCurrentDialogID = dialog?.id as! String
}
override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    APP_DELEGATE.currentVC = nil
    SBDMain.removeChannelDelegate(forIdentifier: self.description)
}
/*override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    
    if !APP_DELEGATE.isFromImageShown {
        self.setChatData()
    }else{
        APP_DELEGATE.isFromImageShown = false
    }
}*/
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    APP_DELEGATE.chatCurrentDialogID = self.channelUrl
}

//MARK: - setUp UI
func setUpUI() {
    self.setChatData()
    
    self.objNav.addShadowLikeAndroidView()
    
    SBDMain.add(self as SBDChannelDelegate, identifier: self.description)
    //SBDMain.add(self as SBUMessageInputViewDelegate, identifier: self.description)
    //self.selectedChannelInfo?.startTyping()
    //self.selectedChannelInfo?.endTyping()
    
    var SELF_USER_ID = ""
    if getFromUserDefaultForKeyByUnArchive(key_IsProviderLogin) == nil {    //User / Patient
        SELF_USER_ID = APP_DELEGATE.objUser["mobile"].stringValue
    }
    else {  //Provider
        SELF_USER_ID = APP_DELEGATE.objUser["email"].stringValue
    }
    
    var arrTempUserData = [SBDUser]()
    arrTempUserData = self.selectedChannelInfo?.members as! [SBDUser]
    arrTempUserData = arrTempUserData.filter { (user) -> Bool in
        return user.userId != SELF_USER_ID
    }
    
    if arrTempUserData.count>0 {
        self.lblName.text  = (arrTempUserData[0]).nickname
        self.imgProfile.sd_setImage(with: URL(string: (arrTempUserData[0]).profileUrl ?? ""), placeholderImage: UIImage(named: "ico_profile.png"), options: .scaleDownLargeImages, context: nil)
    }
    else {
        self.lblName.text  = ""
        self.imgProfile.sd_setImage(with: URL(string: ""), placeholderImage: UIImage(named: "ico_profile.png"), options: .scaleDownLargeImages, context: nil)
    }
    
    self.lblTyping.isHidden = true
    
    if self.IsCallingContinue {
        self.btnAudioCall.isHidden = true
        self.btnVideoCall.isHidden = true
        
        self.imgProfile.isHidden = false
        self.cnstImgProfileHeight.constant = 40.0
        
    }else{
        self.btnAudioCall.isHidden = false
        self.btnVideoCall.isHidden = false
        
        self.imgProfile.isHidden = false
        self.cnstImgProfileHeight.constant = 40.0
    }
    
}

//MARK: - Set Chat Data
func setChatData() {
    self.openChannelVC(channelUrl: self.channelUrl)
}
func openChannelVC(channelUrl: String, messageListParams: SBDMessageListParams? = nil) {
    let channelVC = SBUChannelViewController(
        channelUrl: channelUrl,
        messageListParams: messageListParams
        
    )
    channelVC.navigationController?.isToolbarHidden = true
    channelVC.navigationController?.isNavigationBarHidden = true
    
    self.addChild(channelVC)
    
    SBUTheme.messageCellTheme.adminMessageFont = .systemFont(ofSize: 15)
    SBUTheme.messageCellTheme.userMessageFont = .systemFont(ofSize: 15)
    SBUTheme.messageCellTheme.rightBackgroundColor = UIColor(hexString: "218271")!
    SBUTheme.messageCellTheme.rightBackgroundColor = UIColor(hexString: "218271")!
    SBUTheme.messageCellTheme.fileIconColor = UIColor(hexString: "218271")!
    SBUTheme.messageCellTheme.fileImageIconColor = UIColor(hexString: "218271")!
    SBUTheme.messageInputTheme.buttonTintColor = UIColor(hexString: "218271")!
    SBUTheme.messageInputTheme.textFieldTintColor = UIColor(hexString: "218271")!
    SBUTheme.componentTheme.loadingSpinnerColor = UIColor(hexString: "218271")!
    
    SBUIconSet.iconAdd = UIImage(named: "icon_attachment.png")!
    
    channelVC.view.frame = self.viewChat.bounds
    viewChat.addSubview(channelVC.view)
    channelVC.didMove(toParent: self)
}

}

//MARK: - Extension for Button Click
extension SBParentChatVC {
@IBAction func btnClicked(_ sender: UIButton) {
self.view.endEditing(true)
switch sender.tag {
case 1: //back
var isIncludingMessageList : Bool = false
var messageListVC = UIViewController()
for controller in self.navigationController!.viewControllers as Array {
if controller.isKind(of: MessageListVC.self) {
self.navigationController!.popToViewController(controller, animated: true)
messageListVC = controller
isIncludingMessageList = true
break
}
}
if isIncludingMessageList {
self.navigationController?.popToViewController(messageListVC, animated: true)
}else{
self.navigationController?.popViewController(animated: true)
}
case 2: //audio call
print(“audio call”)
//Sendbird - 9spl - audio
self.didTapVoiceCall()
case 3: //video call
print(“video call”)
//Sendbird - 9spl - video
self.didTapVideoCall()
default:
break;
}
}
}

// MARK: - Audio and Video call
extension SBParentChatVC {
func didTapVoiceCall() {
var SELF_USER_ID = “”
if getFromUserDefaultForKeyByUnArchive(key_IsProviderLogin) == nil { //User / Patient
SELF_USER_ID = APP_DELEGATE.objUser[“mobile”].stringValue
}
else { //Provider
SELF_USER_ID = APP_DELEGATE.objUser[“email”].stringValue
}

    var arrTempUserData = [SBDUser]()
    arrTempUserData = self.selectedChannelInfo?.members as! [SBDUser]
    arrTempUserData = arrTempUserData.filter { (user) -> Bool in
        return user.userId != SELF_USER_ID
    }
    var calleeId = ""
    if arrTempUserData.count>0 {
        calleeId  = (arrTempUserData[0]).userId
    }
    else {
       return
    }
    if calleeId == "" {
        self.presentErrorAlert(message: DialErrors.emptyUserID.localizedDescription)
        return
    }
    
    SVProgressHUD.show()
    self.btnAudioCall.isEnabled = false
    //self.indicator.startLoading(on: self.view)
    
    // MARK: SendBirdCall.dial()
    let callOptions = CallOptions(isAudioEnabled: true)
    let dialParams = DialParams(calleeId: calleeId, isVideoCall: false, callOptions: callOptions, customItems: [:])

    SendBirdCall.dial(with: dialParams) { call, error in
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            self.btnAudioCall.isEnabled = true
            //self.indicator.stopLoading()
            SVProgressHUD.dismiss()
        }
        
        guard error == nil, let call = call else {
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.presentErrorAlert(message: DialErrors.voiceCallFailed(error: error).localizedDescription)
            }
            return
        }
        
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            
            SVProgressHUD.dismiss()
            let vc = storyboardName.storyBoard_CallMain.instantiateViewController(withIdentifier: "\(VoiceCallViewController.self)") as! VoiceCallViewController
            vc.call = call
            vc.isDialing = true
            self.navigationController?.pushViewController(vc, animated: true)
            //self.performSegue(withIdentifier: "voiceCall", sender: call)
        }
    }
}

func didTapVideoCall() {
    /*guard let calleeId = calleeIdTextField.text?.collapsed else {
        self.presentErrorAlert(message: DialErrors.emptyUserID.localizedDescription)
        return
    }*/
    var SELF_USER_ID = ""
    if getFromUserDefaultForKeyByUnArchive(key_IsProviderLogin) == nil {    //User / Patient
        SELF_USER_ID = APP_DELEGATE.objUser["mobile"].stringValue
    }
    else {  //Provider
        SELF_USER_ID = APP_DELEGATE.objUser["email"].stringValue
    }
    
    var arrTempUserData = [SBDUser]()
    arrTempUserData = self.selectedChannelInfo?.members as! [SBDUser]
    arrTempUserData = arrTempUserData.filter { (user) -> Bool in
        return user.userId != SELF_USER_ID
    }
    var calleeId = ""
    if arrTempUserData.count>0 {
        calleeId  = (arrTempUserData[0]).userId
    }
    else {
       return
    }
    if calleeId == "" {
        self.presentErrorAlert(message: DialErrors.emptyUserID.localizedDescription)
        return
    }
    
    SVProgressHUD.show()
    self.btnVideoCall.isEnabled = false
    //self.indicator.startLoading(on: self.view)
    
    // MARK: SendBirdCall.dial()
    let callOptions = CallOptions(isAudioEnabled: true, isVideoEnabled: true, useFrontCamera: true)
    let dialParams = DialParams(calleeId: calleeId, isVideoCall: true, callOptions: callOptions, customItems: [:])

    SendBirdCall.dial(with: dialParams) { call, error in
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            self.btnVideoCall.isEnabled = true
            //self.indicator.stopLoading()
            SVProgressHUD.dismiss()
        }
        
        guard error == nil, let call = call else {
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.presentErrorAlert(message: DialErrors.videoCallFailed(error: error).localizedDescription)
            }
            return
        }
        
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            
            SVProgressHUD.dismiss()
            let vc = storyboardName.storyBoard_CallMain.instantiateViewController(withIdentifier: "\(VideoCallViewController.self)") as! VideoCallViewController
            vc.call = call
            vc.isDialing = true
            self.navigationController?.pushViewController(vc, animated: true)
            //self.performSegue(withIdentifier: "videoCall", sender: call)
        }
    }
}

}

// MARK: - SBUMessageInputViewDelegate
extension SBParentChatVC : SBUMessageInputViewDelegate {

func messageInputViewDidStartTyping() {
    //self.channelViewModel?.startTypingMessage()
    self.selectedChannelInfo?.startTyping()
}

func messageInputViewDidEndTyping() {
    //self.channelViewModel?.endTypingMessage()
    self.selectedChannelInfo?.endTyping()
}


/*
override func messageInputViewDidStartTyping() {
    super.messageInputViewDidStartTyping()
    self.selectedChannelInfo?.startTyping()
}
    
override func messageInputViewDidEndTyping() {
    super.messageInputViewDidEndTyping()
    self.selectedChannelInfo?.endTyping()
}
 */

}

// MARK: - SBDChannelDelegate
extension SBParentChatVC: SBDChannelDelegate {
func channelDidUpdateTypingStatus(_ sender: SBDGroupChannel) {
if sender.channelUrl == self.selectedChannelInfo!.channelUrl {
let members = sender.getTypingMembers()
var SELF_USER_ID = “”
if getFromUserDefaultForKeyByUnArchive(key_IsProviderLogin) == nil { //User / Patient
SELF_USER_ID = APP_DELEGATE.objUser[“mobile”].stringValue
}
else {
SELF_USER_ID = APP_DELEGATE.objUser[“email”].stringValue
}

        var arrTempUserData = [SBDMember]()
        arrTempUserData = members as! [SBDMember]
        arrTempUserData = arrTempUserData.filter { (user) -> Bool in
            return user.userId != SELF_USER_ID
        }
        
        if arrTempUserData.count>0 {
            self.lblTyping.isHidden = false
        }
        else {
            self.lblTyping.isHidden = true
        }
    }
}

}

============================================================

Thank you.

Hello @Ibrahim_Malada,
Sorry for the late reply.

I checked your code.
First, as mentioned before, it is not possible to handle the SBUMessageInputViewDelegate functions of the SBUChannelViewController on SBParentChatVC.
To handle the SBUMessageInputViewDelegate functions, you must override the functions in the custom class that inherited the SBUChannelViewController.
I think you can initialize using your selectedChannelInfo object.

And next, If the SBUChannelViewController is loaded without problems, you don’t have to call startType(), endType() directly because it’s sending typing status internally.

Finally, I tested it similar to the structure you are implementing, but it worked without any problems.
Please check the below codes.

// Present: Channel list 
let vc = CustomChannelListVC()
let naviVC = UINavigationController(rootViewController: vc)
self.present(naviVC, animated: true)
// Customized `SBUChannelListViewController` for using `CustomChannelViewController`.

import UIKit
import SendBirdUIKit

class CustomChannelListVC: SBUChannelListViewController {

    override func showChannel(channelUrl: String, messageListParams: SBDMessageListParams? = nil) {
        let channelVC = CustomChannelVC(
            channelUrl: channelUrl,
            messageListParams: messageListParams
        )
        // If your case, use the below
        // let channelVC = CustomChannelVC(channel: self.selectedChannelInfo)
        channelVC.view.frame = self.view.bounds
        self.addChild(channelVC)
        self.view.addSubview(channelVC.view)
        channelVC.didMove(toParent: self)
    }
}
// Customized `SBUChannelViewController` for using `SBUMessageInputViewDelegate`.


import UIKit
import SendBirdUIKit

class CustomChannelVC: SBUChannelViewController {

    override func messageInputViewDidEndTyping() {
        print("messageInputViewDidEndTyping")
    }
    
    override func messageInputViewDidStartTyping() {
        print("messageInputViewDidStartTyping")
    }
}

Please check and reply :grinning: :pray:

1 Like

Hello @Tez

For your reference, I have added classes from the uikit and then able to send the typing status.

Thank you.

1 Like