With the release of the Chat SDK v2 in April of 2020, many of you may be asking where the floating message counter went. In Chat SDK v1, the counter showed users how many new messages they'd received while not on the Chat screen. This is a pattern that we decided to move away from in Chat SDK v2 for a number of reasons. The primary reason is that providing a floating button directly in apps caused a lot of design issues that quickly became hard for integrators to resolve.

But there's good news. Chat SDK v2 has the ability to retrieve the data that you need to create a message counter button of your own using the following steps for iOS.

The API to implement this feature is currently only available on iOS. The article will be updated with Android code snippets once they are available.

Getting started

This tutorial assumes you already have some knowledge about how the Chat SDK is embedded and implemented. If you haven't embedded the SDK before,the quick start guideand thedocumentationwould be good resources to use before progressing through this article.

Initial setup

There are a few steps that need to be implemented before diving into the core logic of the feature.

  1. You'll need access to the Zendesk Chat dashboard to get the Chat account key. If you don't have access, ask somebody who does to retrieve it for you.

  2. Embed the frameworks into your application. This can be done manually or through your preferred dependency manager. SeeAdding the Chat SDKfor more details.

  3. Add the initialization code to yourAppDelegate.swiftfile. First, import the API provider layer of the SDK:

                   
    importChatProvidersSDK

    Second, initialize theChatinstance with your Chat account key in thedidFinishLaunchdelegate method:

                   
    funcapplication(_ application:UIApplication,didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{Chat.initialize(accountKey:"your_account_key")returntrue}

Once you've followed these steps, you're ready to dive into the message counter feature. See thedocumentationfor more details on the previous steps.

Creating a Zendesk wrapper

You'll probably want to customize the look and flow of users' chat experience. This can be done through theConfigurationclasses and the Chat APIs. As a result, a lot of Zendesk-related code can become spread across your code base. We recommend creating a wrapper that can contain the logic of your Zendesk related code.

             
importChatSDKimportChatProvidersSDKimportCommonUISDKimportMessagingSDKfinalclassZendeskChat{staticletinstance=ZendeskChat()privatevarchatConfiguration:ChatConfiguration{letchatConfiguration=ChatConfiguration()chatConfiguration.isOfflineFormEnabled=falsechatConfiguration.isAgentAvailabilityEnabled=truechatConfiguration.chatMenuActions=[.endChat]chatConfiguration.preChatFormConfiguration=.init(name:.required,email:.optional,phoneNumber:.hidden,department:.hidden)returnchatConfiguration}privatevarmessagingConfiguration:MessagingConfiguration{letmessagingConfiguration=MessagingConfiguration()// You can change the name of the bot that appears in the chat here.messagingConfiguration.name="your_brand_bot_name"returnmessagingConfiguration}privatevarchatAPIConfiguration:ChatAPIConfiguration{letchatAPIConfiguration=ChatAPIConfiguration()chatAPIConfiguration.department="Sales"chatAPIConfiguration.tags=["iOS","v2","messageCounter"]returnchatAPIConfiguration}funcsetupStyle(){CommonTheme.currentTheme.primaryColor=.black}funccreateChatViewController()throws->UIViewController{setupStyle()letchatEngine=tryChatEngine.engine()Chat.instance?.configuration=chatAPIConfigurationletviewController=tryMessaging.instance.buildUI(engines:[chatEngine],configs:[messagingConfiguration,chatConfiguration])returnviewController}}

Exposing Zendesk events to the rest of your app

You'll probably want to leverage some of these Zendesk-related events in parts of your app. To do this in a consolidated and confined approach, we recommend creating a delegate to handle these events appropriately. To decouple the message counter logic from your Zendesk wrapper, you should create aZendeskChatDelegatedelegate.

Add the following delegate to the top ofZendeskChat.swift:

             
protocolZendeskChatDelegate:class{funcwillShowConversation(forchat:ZendeskChat)funcwillCloseConversation(forchat:ZendeskChat)funcunreadMessageCountChanged(numberOfUnreadMessages:Int,inchat:ZendeskChat)}

Add a delegate property to your wrapper.

             
weakvardelegate:ZendeskChatDelegate?

Listening to user events

The Chat SDK interacts with the Unified SDK through theChatEngineclass that is instantiated and passed into the viewController build method. Among other things, theChatEnginecontrols the lifecycle of the chat WebSocket connection. The current implementation of theChatEnginedisconnects the WebSocket connection once the user leaves the chat screen. This is intended behavior.

To implement the unread message counter, you need to reopen this connection and manage the state of the connection when the user leaves the screen or the app. The Unified SDK allows you to listen to user events through theMessagingDelegate. You'll leverage this API to know when a user performs certain events.

Extend yourZendeskChatwrapper to conform to the MessagingDelegate protocol and implement its methods. At the moment, you should only care about the following events:viewWillAppear,viewWillDisappear, andviewControllerDidClose. These can have empty implementations for now.

             
extensionZendeskChat:MessagingDelegate{funcmessaging(_ messaging:Messaging,didPerformEvent event:MessagingUIEvent,context:Any?){switchevent{case.viewWillAppear:delegate?.willShowConversation(for:self)case.viewWillDisappear:delegate?.willCloseConversation(for:self)case.viewControllerDidClose:breakdefault:break}}funcmessaging(_ messaging:Messaging,shouldOpenURL url:URL)->Bool{true// default implementation opens in Safari }}

You need to inform theMessaginginstance that theZendeskChatwrapper is the class that will handle the user events. Add an initializer toZendeskChat我分配nstance of the delegate to the Messaging instance.

             
init(){Messaging.instance.delegate=self}

Now the Zendesk wrapper is practically ready to implement the message counter.

Creating the message counter

The Zendesk wrapper will be extended and leveraged to implement the message counter later in this tutorial. First off, you'll need to some UI to showcase the feature unread counter feature. DownloadMessageOverlay.swiftand drag it into your project. This file contains a subclass ofUIWindowthat you can use to display the new messages and relevant updates.

Once you've added the UI layer, you're ready to start implementing the core functionality of the message counter.

Observing notifications

For this feature to work with unexpected behavior, you need to handle theUIApplicationevents, disconnecting and connecting appropriately. To do this, we've prepared a helper protocol for you to implement. DownloadNotificationCenterObserver.swiftand drag it into your project.

This protocol has default implementations to help simplify the class that handles the UI events and WebSocket connection. Once you've added it, you'll be ready to create the Message counter class.

Chat connection and state observers

You now have a well established Chat wrapper and a UI layer to implement the feature. But first, you need to piece these two layers together. There are a few steps you need to follow to handle the lifecycle of the chat session and connection. This is due to the aforementioned fact the ChatEngine disconnects from the WebSocket connection when the user leaves the screen.

Create a new file calledZendeskChatMessageCounter.swift. This will handle the core logic of this feature. It has the following responsibilities:

  • Store the closure that will be fired every time the message count is fired
  • Handle the observation tokens forUIApplication,ChatConnection, andChatStateevents.
  • Manage the connection life-cycle, through theConnectionStatusobserver, and theConnectionProvidermethods such asconnectanddisconnect.
  • 处理从ChatState ob ChatState更新server. This is where the unread messages are derived from.

Note:Make sure the class conforms to theNotificationCenterObserverprotocol. You don't need to implement the methods. It will use the default implementations for each of them.

             
importChatProvidersSDKimportChatSDKimportMessagingSDKfinalclassZendeskChatMessageCounter:NotificationCenterObserver{// Called every time the unread message count has changedvaronUnreadMessageCountChange:((Int)->Void)?varisActive=false{didSet{ifisActive==false{stopMessageCounter()}}}/ /标记: Observations/// Collection of token objects to group NotificationCentre related observationsvarnotificationTokens:[NotificationToken]=[]/// Collection of `ObservationToken` objects to group Chat related observationsprivatevarobservations:ObserveBehaviours?/ /标记: Chatprivateletchat:ChatprivatevarisChatting:Bool?{guard connectionStatus==.connectedelse{returnnil}returnchatState?.isChatting==true}privatevarchatState:ChatState?{chat.chatProvider.chatState}privatevarconnectionStatus:ConnectionStatus{chat.connectionProvider.status}/ /标记: Unread messagesprivatevarlastSeenMessage:ChatLog?varunreadMessages:[ChatLog]?{guardisActive&&isChatting==true,letchatState=chatState,letlastSeenMessage=lastSeenMessageelse{returnnil}returnchatState.logs.filter{$0.participant==.agent}.filter{$0.createdTimestamp>lastSeenMessage.createdTimestamp}}private(set)varnumberOfUnreadMessages=0{didSet{ifoldValue!=numberOfUnreadMessages&&isActive{onUnreadMessageCountChange?(numberOfUnreadMessages)}}}init(chat:Chat){self.chat=chat}// To stop observing we have to call unobserve on each observerprivatefuncstopObservingChat(){observations?.unobserve()observations=nilnotificationTokens.removeAll()}privatefuncobserveConnectionStatus()->ObserveBehaviour{chat.connectionProvider.observeConnectionStatus{[weak self](status)inguardletself=selfelse{return}guard status==.connectedelse{return}_=self.observeChatState()}.asBehaviour()}privatefuncobserveChatState()->ObserveBehaviour{chat.chatProvider.observeChatState{[weak self](state)inguardletself=selfelse{return}guard self.connectionStatus==.connectedelse{return}ifstate.isChatting==false{self.stopMessageCounter()}ifself.isActive{self.updateUnreadMessageCount()}}.asBehaviour()}/ /标记: Connection life-cycleprivatefuncconnect(){guard connectionStatus!=.connectedelse{return}chat.connectionProvider.connect()}privatefuncdisconnect(){chat.connectionProvider.disconnect()}funcconnectToChat(){guard isActiveelse{return}connect()startObservingChat()}/ /标记: Message counterfuncstartMessageCounterIfNeeded(){guard isChatting==true&&!isActiveelse{return}lastSeenMessage=chatState?.logs.lastupdateUnreadMessageCount()}funcstopMessageCounter(){stopObservingChat()resetUnreadMessageCount()disconnect()}privatefuncupdateUnreadMessageCount(){numberOfUnreadMessages=unreadMessages?.count??0}privatefuncresetUnreadMessageCount(){numberOfUnreadMessages=0}}extensionZendeskChatMessageCounter{/ /标记: Observations//We observe the connection and once it successfully connects we can start observing the state of the chat.privatefuncstartObservingChat(){observations=ObserveBehaviours(observeConnectionStatus(),observeChatState())observeNotification(withName:Chat.NotificationChatEnded){[weak self]_inself?.stopMessageCounter()}observeApplicationEvents()}privatefuncobserveApplicationEvents(){observeNotification(withName:UIApplication.didEnterBackgroundNotification){[weak self]_inself?.disconnect()}observeNotification(withName:UIApplication.willEnterForegroundNotification){[weak self]_inifself?.isActive==true{self?.connect()}}observeNotification(withName:Chat.NotificationChatEnded){[weak self]_inifself?.isActive==true{self?.stopMessageCounter()}}}}

Tying it all together

The heavy lifting is done. Your users will soon be able to receive chat message notifications when they're browsing your app. But there are some small important details that you need to add to yourZendeskChat包装器在使用功能。

  1. Add the following properties to the class:

                   
    / /标记: unread message counterprivatevarmessageCounter:ZendeskChatMessageCounter?varisUnreadMessageCounterActive=false{didSet{messageCounter?.isActive=isUnreadMessageCounterActive}}varnumberOfUnreadMessages:Int{messageCounter?.numberOfUnreadMessages??0}
  2. Update the initializer to initialize themessageCounter.

                   
    init(){Messaging.instance.delegate=selfguardletchat=Chat.instanceelse{return}messageCounter=ZendeskChatMessageCounter(chat:chat)messageCounter?.onUnreadMessageCountChange={[weak self]numberOfUnreadMessagesinguardletself=selfelse{return}// Notify delegateself.delegate?.unreadMessageCountChanged(numberOfUnreadMessages:numberOfUnreadMessages,in:self)}}
  3. Update theMessagingDelegateextension to handle the counter appearing and hiding.

                   
    funcmessaging(_ messaging:Messaging,didPerformEvent event:MessagingUIEvent,context:Any?){switchevent{case.viewWillAppear:delegate?.willShowConversation(for:self)case.viewWillDisappear:messageCounter?.startMessageCounterIfNeeded()delegate?.willCloseConversation(for:self)case.viewControllerDidClose:messageCounter?.connectToChat()default:break}}

Once you've done this, your application will successfully manage the lifecycle of the Chat SDK outside of the chat screen. The final step involves conforming to theZendeskChatDelegate. This should be done in one of your view controllers. This allows you to update the UI accordingly.

  1. Add reference to a MessageOverlay object to your ViewController.

                   
    privatevarmessageOverlay:MessageCounterOverlay?
  2. Update theviewDidLoadto create an instance of the overlay.

                   
    override funcviewDidLoad(){super.viewDidLoad()ZendeskChat.instance.delegate=selfmessageOverlay=MessageCounterOverlay()messageOverlay?.onTap={[weak self]inguardletviewController=ZendeskChat.instance.createChatViewController()else{return}letchatNavigationController=UINavigationController(rootViewController:viewController)self?.present(chatNavigationController,animated:true,completion:nil)}}
  3. Implement the delegate methods updating the UI appropriately.

                   
    extensionViewController:ZendeskChatDelegate{funcwillShowConversation(forchat:ZendeskChat){messageOverlay?.hide()/// Called when conversation will appear}funcwillCloseConversation(forchat:ZendeskChat){messageOverlay?.show()/// Called when conversation will disappearZendeskChat.instance.isUnreadMessageCounterActive=true}funcunreadMessageCountChanged(numberOfUnreadMessages:Int,inchat:ZendeskChat){messageOverlay?.updateNewMessageCount(numberOfUnreadMessages)}}