Build an iOS chat app using Swift and Chatkit - Part 4: Add online presence and typing indicators
To follow this series you will need Xcode, Cocoapods, PHP and Laravel installed on your machine. Some knowledge of Xcode and Swift will be helpful.
IMPORTANT:
ChatKit has been retired from the Pusher product stack as of April, 2020. This tutorial will no longer be functional in its current version. Explore our other Pusher tutorials to find out how Channels and Beams can help you build.
In the previous part of the series, we created our chat application with the normal basic messaging features. At this point, our application allows us to log in, sign up, add contacts and send messages to our contacts.
In this part of the series, we will add some extra functionality. We will add user presence and typing notification to our chat application.
Requirements
To follow along in this final part of the series, you need to have completed all three preceding parts of the series. If you have not, then this part will not make much sense to you. If you have, let’s continue.
💡 At this point, make sure your application runs properly on your simulator. Messaging and other parts of the application should be working correctly.
Adding a user is typing notification
In modern chat applications, you will usually be notified when a user is typing a message. This makes the chat even more engaging, mainly because if you wanted to leave the application but see that the user you messaged is typing a response, you are more likely to stay and see what the user is about to send to you.
Let’s add this feature to our current application to make it more engaging for the user. Our plan is to listen for when the user is typing in the input bar and trigger the currentUser.typing
method on the Chatkit Swift SDK. This triggers a Pusher event that will be picked up by members of the room using the PCRoomDelegate
’s userStartedTyping
method.
Triggering the user is typing event
If you haven’t already, launch the application in Xcode using the *.xcworkspace
file in the root of your project.
Open the ChatroomViewController
class. This is where we will be triggering the typing event. In the MessageInputBarDelegate, we will conform to another method available to the delegate called messageInputBar(:textViewTextDidChangeTo)
. This method is called every time the text in the input bar changes and will be perfect for emitting the typing event.
In the extension of ChatroomViewController
that specifies the MessageInputBarDelegate
add the method below:
func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) {
guard interactor?.currentUser != nil else { return }
guard let room = router?.dataStore?.contact?.room else { return }
self.interactor?.startedTyping(inRoom: room)
}
In this method, we check if the currentUser
has been set or if the chat manager has connected. Then we get the current room from the datastore, and lastly, we call a startedTyping
method on the interactor
passing the PCRoom
object.
Let’s define the startedTyping
method in the interactor
. Open the ChatroomInteractor.swift
file and in the ChatBusinessLogic
protocol add the definition below:
func startedTyping(inRoom room: PCRoom)
Then in the ChatInteractor
class, add the implementation below:
func startedTyping(inRoom room: PCRoom) {
currentUser?.typing(in: room) { err in
guard err == nil else {
print("Error sending typing indicator: \(err!.localizedDescription)")
return
}
}
}
The method above will trigger the typing
method in the Chatkit SDK, which can then be picked up by others logged in and subscribed to the room
.
Next, let us add the event handler for when a user is typing and when a user stops typing. These are the methods that are called when the typing
method above is called. They will be called automatically so no need to invoke them manually.
In the ChatroomInteractor
extension of the [PCRoomDelegate](https://docs.pusher.com/chatkit/reference/swift#pcroomdelegate)
add the following methods:
func onUserStartedTyping(user: PCUser) {
DispatchQueue.main.async {
self.presenter?.toggleUserIsTyping(for: user.displayName)
}
}
func onUserStoppedTyping(user: PCUser) {
DispatchQueue.main.async {
self.presenter?.toggleUserIsTyping(for: user.displayName)
}
}
In the methods above, we use the presenter to call toggleUserIsTyping
anytime any of the functions above are triggered. Let us define the toggleUserIsTyping
method on the presenter.
Open the ChatroomPresenter.swift
file and in the ChatroomPresentationLogic
add the function definition below:
func toggleUserIsTyping(for name: String)
Next in the ChatroomPresenter
add the method below:
func toggleUserIsTyping(for name: String) {
viewController?.handleTyping(by: name)
}
In the method above, the presenter calls the handleTyping
method on the ChatroomViewController
class. Let us create the method in the controller.
Open the ChatroomViewController.swift
and in the ChatroomDisplayLogic
add the definition below to the protocol:
func handleTyping(by username: String)
Then in the ChatroomViewController
class, conform to the protocol by adding the method and property below to the class:
var isTyping = false
func handleTyping(by username: String) {
defer {
isTyping = !isTyping
}
if isTyping {
messageInputBar.topStackView.arrangedSubviews.first?.removeFromSuperview()
messageInputBar.topStackViewPadding = .zero
} else {
let label = UILabel()
label.text = "\(username) is typing..."
label.font = UIFont.boldSystemFont(ofSize: 13)
messageInputBar.topStackView.addArrangedSubview(label)
messageInputBar.topStackViewPadding.top = 6
messageInputBar.topStackViewPadding.left = 12
messageInputBar.backgroundColor = messageInputBar.backgroundView.backgroundColor
}
}
In the method above we are checking to see if the user is typing. If the user is typing then we display the typing notification.
Right now, when the other user is typing a message, you will get a notification saying the user is typing a message. Once the user stops typing, the notification disappears.
Here is a screen recording of the feature in action:
As you can see, when a user is typing a message, we get the notification. Great, now let’s move on to setting up user presence to know when a user is online.
Adding user presence
User presence is an indication as to whether the user is online or not online at a given time. This improves the user engagement because when you know you have a friend online you are more likely to start a conversation with said friend.
Adding this feature will be broken into two parts:
- Knowing when a user comes online.
- Checking the presence state of the other users at regular intervals.
Knowing when a user comes online
To know when a user comes online we will be conforming to the userCameOnline
and userWentOffline
methods that are in the [PCChatManagerDelegate](https://docs.pusher.com/chatkit/reference/swift#pcchatmanagerdelegate)
protocol. In the implementation we will set the contact to online (or offline) based on which method was called.
Open the ListContactsViewController.swift
file and paste the code below at the bottom of the file:
extension ListContactsViewController: PCChatManagerDelegate {
private func setPresence(for user: PCUser, _ online: Bool) {
DispatchQueue.main.async {
guard let index = self.displayedContacts.index(where: {$0.id == user.id}) else { return }
self.displayedContacts[index].isOnline = online
self.tableView.reloadData()
}
}
func onPresenceChanged(stateChange: PCPresenceStateChange, user: PCUser) {
setPresence(for: user, stateChange.current == .online)
}
}
In the extension of *ListContactsViewController*
above we create a setPresence
function that sets the user presence for a user and reloads the table view so the new data is reflected.
The next method, onUserPresenceChanged
is automatically invoked by Chatkit when a user comes online or offline. When any of them happens we call the setPresence
method that updates the applications UI with the changes.
Here is a screen recording of the feature in action:
As seen in the recording, when the user logs in, the user’s status changes to Online which is what we want.
Keeping the presence state updated
The second part of the user presence feature is keeping the states for all the users updated regularly. For this we will be using the Timer
class to recursively check the presence state of each contact.
This comes in handy when you log in for the first time and there are already people online. Chatkit does not emit any events that give you this information at this time so checking the users
property of the [PCCurrentUser](https://docs.pusher.com/chatkit/reference/swift#pccurrentuser)
class will give us the presence state of each user. We can then use this information to know who is online at the moment and who is not.
In your ListContactsViewController
class, add the following method:
private func updateContactsPresence() {
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
guard let users = self.interactor?.currentUser?.users else { return }
for contact in self.displayedContacts {
guard let user = users.first(where: {$0.id == contact.id}) else { return }
let index = self.displayedContacts.index(of: contact)
switch user.presenceState {
case .online: self.displayedContacts[index!].isOnline = true
case .offline, .unknown: self.displayedContacts[index!].isOnline = false
}
}
self.tableView.reloadData()
}
}
The code above uses the scheduledTimer
method to call the block of code every five seconds. In the block of code, we get the users
from the PCCurrentUser
and then loop through the displayedContacts
. For every contact, we check the presence state and set it for the contact then reload the table view data.
The next thing to do will be to call this function. We will call this function only when we are sure that Chatkit has connected so the timer block does not get called unnecessarily.
In the same file, jump to the initialiseChatkit
method and in the chatManager.connect
block, add the following after the fetchContacts
call:
self.updateContactsPresence()
This will make sure that we register the timer block only after the currentUser
has been registered.
Here is a screen recording of this feature in action:
As seen in the recording, when the second user logs in, the status of the contacts refreshes to online after a while. This is because it is being checked by the timer block we added to the class above.
Conclusion
In this final part of the series, we have been able to add more realtime features to our application. We added the user presence which tells you the online status of your contacts and we also added the typing notification which tells you when a user is typing a message.
Hopefully you have learned how you can use the power of Chatkit to create an entire messenger platform. The repository for the project is available on GitHub.
IMPORTANT:
ChatKit has been retired from the Pusher product stack as of April, 2020. This tutorial will no longer be functional in its current version. Explore our other Pusher tutorials to find out how Channels and Beams can help you build.
5 June 2018
by Neo Ighodaro