Using the PubNub Swift Chat SDK
PubNub's Swift Chat SDK is the recommended way for iOS developers to create new chat applications or add in-app chat to existing applications
Set up environment
This tutorial walks you through setting up a basic sample app created using the PubNub Swift Chat SDK. It explains the app architecture and the code behind the main chat features.
The Swift Chat SDK sample used in this tutorial is available at https://github.com/PubNubDevelopers/Chat-SDK-Tutorial-Swift and is released under MIT. The sample is designed for illustrative purposes only and has minimal error handling.
The sample targets PubNub Swift Chat SDK 1.0.0.
The Swift Chat SDK is UI and framework agnostic, allowing you complete control over how your application looks and how it is architected. The sample application has been written with SwiftUI and some custom styling.
Prerequisites
Make sure you have the following installed on your computer:
Download the source application
In a terminal or command window, navigate to an empty folder. Enter the following command to clone the repository.
git clone https://github.com/PubNubDevelopers/Chat-SDK-Tutorial-Swift.gitOpen the Chat-SDK-Tutorial-Swift/PubNubSwiftChatSDKTutorial folder in Xcode. Xcode should automatically download the required package dependencies, including PubNub Swift Chat SDK 1.0.0.
If package resolution does not start automatically, use File -> Packages -> Resolve Package Versions in Xcode.
The sample stores your PubNub keys in this file:
/PubNubSwiftChatSDKTutorial/PubNubSwiftChatSDKTutorial/Keys.swift
The file currently contains placeholders for your PubNub publish and subscribe keys:
// REPLACE THESE WITH YOUR PUB / SUB KEYS
struct Keys {
static let PUBNUB_PUBLISH_KEY="YOUR_PUBLISH_KEY_HERE"
static let PUBNUB_SUBSCRIBE_KEY="YOUR_SUBSCRIBE_KEY_HERE"
}The next step is to generate your own publish and subscribe keys to replace these placeholders.
Create keys
Demo keys
You're ready to go!
We’ve assigned demo keys which are set up to support the features of this tutorial app. These will be active for 72 hours, but you can sign up for a free account at the end of the tutorial to create your own permanent keys.
The demo keys have the following functionality activated ready for your application:
- App Context this allows you to manage the metadata of your users and channels
- Presence you will be able to track user online and offline status
- Message Persistence You will be able to see user message history and metadata.
My own keys
- You'll need to create an account to use your own keysSign in to your PubNub Dashboard.
- Go to Keysets using the left hand side navigation.
- Click the “Create New Keyset” button on the upper right hand-side of the portal.
- A pop-up appears. Give the keyset a name and click the “Create” button.
- Click on the newly created keyset.
- Enable three features for the keyset to be able to connect to the PubNub Network: App Context, Presence, and Message Persistence.
- Enable App Context by clicking on the toggle to turn it on if it is not already enabled. Select the region closest to you to store your context.
- Enable Presence by clicking on the toggle to turn it on. A pop-up will require that you enter in “ENABLE”. Enter in “ENABLE” in all caps and then press the “Enable” button. You can leave the default settings.
- Enable Message Persistence by clicking on the toggle to turn it on. You can leave the default settings.
- Click on the “Save Changes” button in the bottom right corner of the portal. A confirmation message will appear in the upper-left corner of the portal confirming that these changes have been saved.
- Scroll back to the top of the portal. Copy the Publish key by clicking on the copy icon and paste it below.
- Copy the Subscribe Key by clicking on the copy icon and paste it below.
Build and run
Watch the video or follow the steps below to build and run your application.
Add PubNub Keys
In the previous parts of this tutorial, you cloned the sample application, loaded it into Xcode, and installed dependencies. Locate the file /PubNubSwiftChatSDKTutorial/PubNubSwiftChatSDKTutorial/Keys.swift and replace the publish and subscribe keys with the keys you generated in the previous step.
// REPLACE THESE WITH YOUR PUB / SUB KEYS
struct Keys {
static let PUBNUB_PUBLISH_KEY="YOUR_PUBLISH_KEY_HERE"
static let PUBNUB_SUBSCRIBE_KEY="YOUR_SUBSCRIBE_KEY_HERE"
}Run the Application
Everything is now configured to run the application. Launch two simulators and open the application on both.

Enter a "User ID" and log in.
On a brand-new keyset, the sample creates its initial public channels the first time it runs. If those channels do not appear immediately, log out, then log back in once on each device.
You should see the public channels and the option to launch a direct chat with the other device.

Try the following:
- Open a public channel on both devices and send a message. Notice that the message is received with an almost imperceptible delay. The avatar chosen for your character is randomly selected when the user is first created.
- React to this message by clicking on one of the two emoji. Notice this reaction is sent to all recipients.
- Back out of the public channel and reopen it. Notice that the message history is retained along with any reactions.
- Send a direct message from one recipient to the other. Notice the typing indicator and receipt after the message has been read.
How the Sample App Works
The remaining bulk of this tutorial reviews each feature of the sample application and explains the underlying source code that powers it. Although the sample application uses SwiftUI and some custom styling, the PubNub Swift Chat SDK is framework and UI agnostic, giving you complete control over your application's appearance.
Initializing the Chat SDK
The sample initializes the Chat SDK in HomeScreen.swift by creating a PubNub configuration, creating a Chat SDK configuration, and initializing ChatImpl.
let pubNubConfiguration = PubNubConfiguration(
publishKey: Keys.PUBNUB_PUBLISH_KEY,
subscribeKey: Keys.PUBNUB_SUBSCRIBE_KEY,
userId: userId
)
let chatConfiguration = ChatConfiguration()
chat = ChatImpl(
chatConfiguration: chatConfiguration,
pubNubConfiguration: pubNubConfiguration
)
chat?.initialize { result in
switch result {
case .success(_):
debugPrint("Initialized successfully!")
postInitialize(chat: chat)
case .failure(let error):
debugPrint("Error during initialization: \(error)")
}
}The publishKey and subscribeKey have been previously explained. The userId is a string that uniquely identifies the physical user to PubNub.
After you initialize the Chat SDK for a given user ID, the current User is created for you.
Creating and Displaying Channels
Conversations using the Chat SDK happen over a Channel. This sample uses public channels and direct conversations.
- Public channels are created with
chat.createPublicConversation(). - Direct conversations are created with
chat.createDirectConversation().
On initialization, the sample loads channels whose IDs start with public. It then creates the default public channels if they do not already exist.
chat?.getChannels(filter: "id LIKE \"public*\"", sort: [channelOrder]) {
switch $0 {
case let .success((channels)):
publicChannels = channels.channels
ensureDefaultPublicChannels(existingChannels: channels.channels)
case let .failure(error):
print("Error: \(error)")
}
}The default public channels are created with metadata that includes a profileUrl used by the sample UI.
chat?.createPublicConversation(
channelId: seedChannel.id,
channelName: seedChannel.name,
channelDescription: seedChannel.description,
channelCustom: ["profileUrl": seedChannel.profileUrl]
) {
switch $0 {
case .success(_):
createDefaultPublicChannels(seedChannels, index: index + 1)
case let .failure(error):
debugPrint("Failed to create public conversation: \(error)")
}
}Typically, a client would not create public channels. They would have been previously created on your server, perhaps by an administrator. This sample handles everything in the client to keep the tutorial simple.
BizOps Workspace Channels
If you have access to BizOps Workspace on your account, you will see the created public channels in your Channel management view.

Although not supported by this sample, if you modify channel data, the Chat SDK can receive updates through the Channel Stream Updates APIs.
Displaying and Creating 1:1 Conversations
As part of the initialization logic for the sample application, it queries for the fifteen most recently logged-in users and shows those users in the list of direct chats.
let order : PubNub.ObjectSortField = .init(property: .updated, ascending: false)
chat?.getUsers(sort: [order], limit: 15) {
switch $0 {
case let .success((users, _)):
allUsers = users
case let .failure(error):
debugPrint("Failed to fetch user IDs: \(error)")
}
}This was done to keep the sample app simple. In production, you would have some way for the user to select which user or users they want to chat with. One possible way to do this is to use the same chat.getUsers() API and provide a filter parameter as the user types.
Unlike public conversations, this sample application creates direct conversations when you select the user to talk to.
func userSelected(selectedUser: UserImpl) {
showProgress = true
chat?.createDirectConversation(invitedUser: selectedUser) {
switch $0 {
case let .success(conversationResult):
let channel = conversationResult.channel
activeChannel = channel
showProgress = false
case let .failure(error):
debugPrint("Failed to create direct conversation: \(error)")
}
}
}The chat.createDirectConversation() API accepts a User and returns a Channel. The returned channel has two members: the current user and the invited user.
You can see the created direct channel in BizOps Workspace, provided you have BizOps enabled on your account.

Sending and Receiving Messages
Sending messages is handled at the channel level with the channel.sendText(text:) API.
activeChannel?.sendText(text: messageText) {
result in
switch result {
case .success(let timetoken):
debugPrint("Message sent successfully at \(timetoken)")
case let .failure(error):
debugPrint("Failed to send message: \(error)")
}
}Receiving messages in Swift Chat SDK 1.0.0 uses entity-owned callbacks. The sample listens with channel.onMessageReceived(callback:) and keeps the returned AutoCloseable so it can stop listening when the user leaves the screen.
messageCloseable = activeChannel?.onMessageReceived { message in
debugPrint("Received message: \(message.content.text)")
messages.append(message)
myChannelMembership?.setLastReadMessage(message: message)
}When the view disappears, the sample closes the listener.
messageCloseable?.close()Message History
To retrieve messages sent on a channel before joining or listening, you must have Message Persistence enabled on your PubNub keyset. Messages are only stored for the specified retention period, after which they are deleted.
To retrieve past messages sent on a channel, use channel.getHistory().
activeChannel?.getHistory(count: 20) {
switch $0 {
case let .success(response):
messages.append(contentsOf: response.messages)
if (!response.messages.isEmpty) {
myChannelMembership?.setLastReadMessageTimetoken(response.messages.last!.timetoken)
}
case let .failure(error):
debugPrint("Failed to fetch history data: \(error)")
}
}The sample application loads the past 20 messages for simplicity. In production, you would page responses by calling getHistory() multiple times with appropriate startTimetoken and endTimetoken values.
Message Reactions
The Chat SDK allows you to react to messages and be notified when a reaction has been applied or removed.

To apply or remove an emoji reaction, call message.toggleReaction(reaction:).
message?.toggleReaction(reaction: emoji)There is only a single toggleReaction API. There are no separate APIs for adding and removing a reaction. Emojis are represented as Unicode strings and are treated as strings by the API.
The sample application uses two hardcoded emoji that are displayed on every message. In production, you would likely allow a user to choose which emoji to apply to a message.
To be notified when a reaction is applied to a message, the sample uses one of the Message.streamUpdates APIs on historical and newly received messages.
newMessageUpdateStream = MessageImpl.streamUpdatesOn(messages: messages) { updatedMessages in
debugPrint("Received updates for new messages")
messages = updatedMessages
}Displaying Message Reactions
In Swift Chat SDK 1.0.0, message.reactions returns typed MessageReaction values. Each reaction contains the reaction value, whether it belongs to the current user, the user IDs, and a count.
let count = message?.reactions.first(where: { $0.value == emoji })?.count ?? 0The platform is responsible for displaying emojis. Only exchange emojis that can be displayed on the recipient's platform, or use an appropriate emoji compatibility library.
Read Receipts
Read receipts are available for group and direct channels, but not public channels.

At a high level, read receipts work as follows:
- A client receives a message.
- The receiving client marks that message as read.
- Other clients in the chat are notified that the message has been read.
What does it mean for a message to be read? As an application developer, deciding when a user has read a message is up to you. Most chat applications require the message to be contained within the scrolling view so the user has seen it, and you might also have a way to manually mark messages as read.
To mark a message as read, the Chat SDK exposes the setLastReadMessage API on the Membership object.
The sample marks a message as read when it is received on a channel.
myChannelMembership?.setLastReadMessage(message: message)It also marks the newest historical message as read after loading history.
myChannelMembership?.setLastReadMessageTimetoken(response.messages.last!.timetoken)Fetching and Receiving Read Receipts
Swift Chat SDK 1.0.0 exposes read receipts as typed ReadReceipt values. The sample fetches the current read receipt state and then listens for future updates.
activeChannel?.fetchReadReceipts { result in
switch result {
case let .success(response):
readReceipts = response.receipts
case let .failure(error):
debugPrint("Failed to fetch read receipts: \(error)")
}
}
readReceiptCloseable = activeChannel?.onReadReceiptReceived { receipt in
applyReadReceipt(receipt)
}When a new receipt arrives, the sample replaces the existing receipt for that user or appends it if it is new.
func applyReadReceipt(_ receipt: ReadReceipt)
{
if let existingIndex = readReceipts.firstIndex(where: { $0.userId == receipt.userId })
{
readReceipts[existingIndex] = receipt
}
else
{
readReceipts.append(receipt)
}
}The message UI can then compare a receipt's lastReadTimetoken with the message timetoken to decide whether another user has read the message.
Typing Indicator
Typing indicators are available for direct and group chats, but not public chats.

The SDK handles the tracking and timeout logic usually associated with implementing a typing indicator. As a developer, you tell the SDK when somebody is typing, and the SDK notifies you when the typing status changes.
In the onChange handler for the message input field, the sample calls channel.startTyping().
activeChannel?.startTyping()The sample only starts typing events for non-public channels.
func startTyping() {
if (activeChannel?.type != .public)
{
activeChannel?.startTyping()
}
}It listens for typing changes with channel.onTypingChanged(callback:).
typingCloseable = activeChannel?.onTypingChanged {
typingUsers in
debugPrint("Typing users: \(typingUsers)")
if (typingUsers.isEmpty)
{
somebodyIsTyping = false
typingMessage = ""
}
else
{
somebodyIsTyping = true
typingMessage = "Typing: " + typingUsers.joined(separator: ", ")
}
}As previously described for read receipts, although the API returns a list of user IDs, you will likely want to convert these into names and avatars in production.
You have now finished the tutorial. Click Next below for the next steps.
Congratulations - what's next?
Chat SDK Documentation
- Jump straight into the PubNub Swift Chat SDK to understand the capabilities of the API
- Read the Swift Chat SDK 1.0.0 migration guide for a summary of the API changes introduced in the 1.0.0 release.
- Learn more about why we built the Chat SDK, the concepts, and terminology.
Understand the Source Code
This tutorial has only scratched the surface of the capabilities of our Swift Chat SDK, we are continually adding new features, so check out the source code, available at https://github.com/pubnub/swift-chat-sdk
The source code for the sample app discussed in this tutorial is available from https://github.com/PubNubDevelopers/Chat-SDK-Tutorial-Swift
Chat SDK Demos in other Languages
Experience our fully-featured group chat app for the web, written in TypeScript and Next.js:

Ready to get beyond Proof of Concept?
Schedule a 15 minute chat with one of our experts to get your project moving
Got a question for our developer relations team? Email devrel@pubnub.com
Take it to production on your own keys
Create a free PubNub account to get permanent keys for this app.