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

Step 01

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.

Code
git clone https://github.com/PubNubDevelopers/Chat-SDK-Tutorial-Swift.git


Open 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:

Code
// 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.

Step 02

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

  1. You'll need to create an account to use your own keysSign in to your PubNub Dashboard.
  2. Go to Keysets using the left hand side navigation.
  3. Click the “Create New Keyset” button on the upper right hand-side of the portal.
  4. A pop-up appears. Give the keyset a name and click the “Create” button.
  5. Click on the newly created keyset.
  6. Enable three features for the keyset to be able to connect to the PubNub Network: App Context, Presence, and Message Persistence.
  7. 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.
  8. 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.
  9. Enable Message Persistence by clicking on the toggle to turn it on. You can leave the default settings.
  10. 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. 
  11. Scroll back to the top of the portal. Copy the Publish key by clicking on the copy icon and paste it below.
  12. Copy the Subscribe Key by clicking on the copy icon and paste it below.


Step 03

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.

Swift
// 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.

Tutorial - Swift Chat SDK - Login

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.

Tutorial - Swift Chat SDK - Home Screen

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.

Swift
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.

On initialization, the sample loads channels whose IDs start with public. It then creates the default public channels if they do not already exist.

Swift
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.

Swift
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.

Tutorial - Swift Chat SDK - Public Channels

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.

Swift
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.

Swift
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.

Tutorial - Swift Chat SDK - Direct Channels

Sending and Receiving Messages

Sending messages is handled at the channel level with the channel.sendText(text:) API.

Swift
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.

Swift
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.

Swift
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().

Swift
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.

Tutorial - Swift Chat SDK - Reactions

To apply or remove an emoji reaction, call message.toggleReaction(reaction:).

Swift
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.

Swift
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.

Swift
let count = message?.reactions.first(where: { $0.value == emoji })?.count ?? 0

The 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.

Tutorial - Swift Chat SDK - Read Receipts

At a high level, read receipts work as follows:

  1. A client receives a message.
  2. The receiving client marks that message as read.
  3. 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.

Swift
myChannelMembership?.setLastReadMessage(message: message)

It also marks the newest historical message as read after loading history.

Swift
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.

Swift
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.

Swift
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.

Tutorial - Swift Chat SDK - Typing Indicator

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().

Swift
activeChannel?.startTyping()

The sample only starts typing events for non-public channels.

Swift
func startTyping() {
    if (activeChannel?.type != .public)
    {
        activeChannel?.startTyping()
    }
}

It listens for typing changes with channel.onTypingChanged(callback:).

Swift
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.

Step 04

Congratulations - what's next?

Chat SDK Documentation

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:

Tutorial - Chat SDK - React Chat Demo

Ready to get beyond Proof of Concept?

Schedule a 15 minute chat with one of our experts to get your project moving

Talk to an Expert

Got a question for our developer relations team? Email devrel@pubnub.com

Keep building

Take it to production on your own keys

Create a free PubNub account to get permanent keys for this app.