Build an iOS chat app using Swift and Chatkit - Part 4: Add online presence and typing indicators

Introduction

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:

1func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) {
2        guard interactor?.currentUser != nil else { return }
3        guard let room = router?.dataStore?.contact?.room else { return }
4        self.interactor?.startedTyping(inRoom: room)
5    }

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:

1func startedTyping(inRoom room: PCRoom) {
2        currentUser?.typing(in: room) { err in 
3            guard err == nil else {
4                print("Error sending typing indicator: \(err!.localizedDescription)")
5                return
6            }
7        }
8    }

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:

1func onUserStartedTyping(user: PCUser) {
2        DispatchQueue.main.async {
3            self.presenter?.toggleUserIsTyping(for: user.displayName)
4        }
5    }
6
7    func onUserStoppedTyping(user: PCUser) {
8        DispatchQueue.main.async {
9            self.presenter?.toggleUserIsTyping(for: user.displayName)
10        }
11    }

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:

1func toggleUserIsTyping(for name: String) {
2        viewController?.handleTyping(by: name)
3    }

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:

1var isTyping = false
2
3    func handleTyping(by username: String) {
4        defer {
5            isTyping = !isTyping
6        }
7
8        if isTyping {
9            messageInputBar.topStackView.arrangedSubviews.first?.removeFromSuperview()
10            messageInputBar.topStackViewPadding = .zero
11        } else {
12            let label = UILabel()
13            label.text = "\(username) is typing..."
14            label.font = UIFont.boldSystemFont(ofSize: 13)
15            messageInputBar.topStackView.addArrangedSubview(label)
16            messageInputBar.topStackViewPadding.top = 6
17            messageInputBar.topStackViewPadding.left = 12
18            messageInputBar.backgroundColor = messageInputBar.backgroundView.backgroundColor
19        }
20    }

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:

Create-iOS-Chat-App-Using-Chatkit-whos-typing

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:

1extension ListContactsViewController: PCChatManagerDelegate {
2        private func setPresence(for user: PCUser, _ online: Bool) {
3            DispatchQueue.main.async {
4                guard let index = self.displayedContacts.index(where: {$0.id == user.id}) else { return }
5                self.displayedContacts[index].isOnline = online
6                self.tableView.reloadData()
7            }
8        }
9
10        func onPresenceChanged(stateChange: PCPresenceStateChange, user: PCUser) {
11            setPresence(for: user, stateChange.current == .online)
12        }
13    }

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:

Create-iOS-Chat-App-Using-Chatkit-online-presence

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:

1private func updateContactsPresence() {
2        Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
3            guard let users = self.interactor?.currentUser?.users else { return }
4
5            for contact in self.displayedContacts {
6                guard let user = users.first(where: {$0.id == contact.id}) else { return }
7                let index = self.displayedContacts.index(of: contact)
8
9                switch user.presenceState {
10                case .online: self.displayedContacts[index!].isOnline = true
11                case .offline, .unknown: self.displayedContacts[index!].isOnline = false
12                }
13            }
14
15            self.tableView.reloadData()
16        }
17    }

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:

Create-iOS-Chat-App-Using-Chatkit-updating-presence

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.