Getting Started - Swift

Project overview

This quick start will take you through the basics of using Chatkit with Swift. At the end of it you will have a working implementation of a 1:1 chat, powered by Chatkit.

You should have some experience with iOS development and Swift. You will also use some Git as well as Node.js for some additional scripting.
You will need the latest version of Xcode installed, as well as Git, Cocoapods, and Node.JS - 8 or above.

In this exercise, you will build an app with a single chat room with two users in it — Alice and Bob — who can message each other in realtime. You will learn how to interact with the Chatkit API and connect various features together—such as users, messages, and the chatroom itself.

The finished app will look like the one in the animation below.

The app you will be building

Preparation

Installing dependencies

To begin, get the sample repository using Git:github.com/pusher/chatkit-quickstart-swift.
The repository contains two directories - app and server. The first one is the iOS app, and the other contains some scripts you will use to create your chatting environment.

Navigate into the app directory and install the dependencies by running pod install and opening the newly generated XCWorkspace. The only pod you are installing is PusherChatkit.

Navigating the project

There are 3 files of interest that we have prepared for you:

  • ChatroomViewController.swift
  • Main.Storyboard
  • Chatkit.plist

At present, these files contain placeholder implementations.

In the Main.Storyboard, the screen represents a chatroom with a table view that holds messages in rows, as well as a message entry text box and a Send button for sending messages.

In the ChatroomViewController, the view outlets and actions are already configured. So are the helper methods for reading properties fromChatkit.plist and an extension method for loading images from a URL string.

Chatkit.plist contains placeholder constants for accessing your Chatkit instance.

Now that you have a basic understanding of our project, let’s set up Chatkit. First, you need to create a Chatkit instance.

Creating a Chatkit instance

Head on to dash.pusher.com, and click on the Create button in the Chatkit box. Give your instance a name.

name your instance

Clicking the create button will take you to your instance’s dashboard. Click on the Credentials tab under your instance name, where you’ll see 2 long strings - Instance Locator, and Secret Key.
They are unique for each instance. The instance locator lets your app connect to your Chatkit instance, and you will use the Secret Key on the server to authenticate clients. More on that in a bit.

your credentials

Creating users and rooms

Now that you have created and configured a new Chatkit instance, you will learn how to use a Chatkit server SDK to create some users, a chatroom, and populate it with messages.

In the directory that you cloned from GitHub, there is a serversubdirectory. Use your terminal app to cd back into it.

First, run npm install in that directory to install required dependencies. In this case, that’s just the @pusher/chatkit-serverpackage. This is our server SDK for Node.

Next, open the chatkit.js file in your editor, and replace the valuesYOUR_INSTANCE_LOCATOR and YOUR_SECRET_KEY with the instance locator and secret key values from your instance’s dashboard.
This file configures your Chatkit SDK for Node with the correct values, and exports the created Chatkit instance, so that we can re-use it elsewhere with a single import.

Open the create-environment.js file. It is a script that contains a single function create() that will perform the following in your Chatkit instance:

  • Create two users, alice and bob. You will later be able to log in using their user IDs.
  • Create a private room with the ID alice;bob with alice and bob as members of this room.
  • Create a series of messages between the two users in the chatroom created in the previous step.

To run this script, type npm run create-environment in your terminal. A few seconds later the script should finish successfully after creating everything.

These are only a few of the operations you can do with Chatkit Node SDK. You can also do most of them using client SDKs, such as the one for Swift. The only operation that is exclusive to the server is createUsers. User management is considered an admin operation and is not exposed to clients. You can read more about what’s available for you in the Chatkit Node SDK in the docs.

You’re done with the Node SDK for now, but before moving onto the app part of this guide, have a look at another feature of the Chatkit dashboard - the instance inspector.

Using the instance inspector

Go back to your Chatkit dashboard, and select your instance, and click on the Console tab. This is your instance inspector, and is a helpful tool that helps you quickly glance at your Chatkit setup. You can use it to see the users, rooms, messages, and roles. In your case, you should see two users alice and bob who are both members of the room named Alice A, Bob B.

Although the instance inspector lets you perform some of the user actions that you can do with the SDKs, you’ve done it with the Node SDK as it’s more similar to how you would do it in a “real” app.

instance inspector

Developing the app

Instantiating Chatkit

Note: You will add all your logic to file ChatroomViewController.swift. This will illustrate how different parts of the Chatkit SDK work together. In practice, you will likely split the implementation across several files.

First, import the Chatkit package: PusherChatkit.

1
 import PusherChatkit

Next, add two class variables to the ChatroomViewController class: chatManager, and currentUser.

1
2
 private var chatManager: ChatManager?
 private var currentUser: PCCurrentUser?

The ChatManager is your initial entry point into Chatkit, and the Current User is your user’s own context after you've connected to Chatkit. You will make all API interactions with Chatkit Swift as the user you’re connecting with. Remember, we created our first users earlier with the server SDK.

Now, in viewDidLoad method, add the following code to instantiate your ChatManager object below the line TODO: Init Chatkit:

1
 //TODO: Init Chatkit
2
3
4
5
6
 self.chatManager = ChatManager(
       instanceLocator: chatkitInfo.instanceLocator,
       tokenProvider: PCTokenProvider(url: chatkitInfo.tokenProviderEndpoint),
       userID: chatkitInfo.userId
 )

The ChatManager constructor takes three arguments:

  • instanceLocator defines which instance it’s connecting to.
  • tokenProvider is the object that fetches valid user tokens. Its url parameter is the endpoint from which it fetches the tokens.
  • userId is the ID of the Chatkit user that your app will connect as. The user needs to exist in your Chatkit instance before connecting with their ID.

A word on token providers

To make any request to the Chatkit API, you will require a valid token, that is signed by your secret key. You will usually generate these tokens in your backend using the server SDK.

In the client you can use an instance of PCTokenProvider. It takes an endpoint it requests tokens from, and theChatManager uses these tokens to perform your actions.

For now, we provide you with a Test Token Provider endpoint, that you can enable for testing and development purposes. In a later step you’ll implement a real token provider using the server SDK.

To enable the test token provider, you need to check the box in the dashboard that can be found in the Credentials tab, below the instance locator and the key, as seen in the screenshot below. The dashboard generates a Test Token Provider endpoint for you.

You can read more about how Chatkit performs authentication in the docs.

test token provider inspector

Adding your own credentials

All three values you need to instantiate the ChatManager (instanceLocator, url for the token provider, and userId) come from the properties in Chatkit.plist and get loaded by the utility method plistValues. We already prepared that method for you, so you don’t have to do anything at this point.

The currentUser that you get after successfully connecting is the user with that ID. In your app, you have created two valid users, alice and bob, and will connect as one of them.

Open Chatkit.plist and replace the value ofChatkitTokenProviderEndpoint with your newly generated endpoint in the dashboard. Also replace theChatkitInstanceLocator value with the value of your Instance Locator, which is found on the same page in the dashboard.

Connecting to Chatkit

Now that you have instantiated your ChatManager, you can use it to connect to the Chatkit service, which will give you the current user when successfully connected.

First, under the class variables, add a new inner class: an implementation of the ChatManagerDelegate protocol. You will use this class when connecting to Chatkit.

1
2
3
4
5
 class MyChatManagerDelegate: PCChatManagerDelegate {
   func onError(error: Error) {
     print("Error in Chat manager delegate! \(error.localizedDescription)")
   }
 }

The chat manager delegate lets you react to events relating to your Chatkit connection, such as presence changes, room existence, membership updates, and more. All options are listed in the docs. At the moment, the implementation only has hooks that catch any errors that might occur when connecting to Chatkit.

To connect to Chatkit, you need to call chatManager.connect(), which takes aPCChatManagerDelegate as an argument and a callback for your currentUser.

Now, finish the initChatkit method implementation by calling connect and passing a new instance ofMyChatManagerDelegate as a delegate argument:

1
2
3
4
5
6
7
8
  //Init Chatkit
  self.chatManager = ChatManager(
      instanceLocator: chatkitInfo.instanceLocator,
      tokenProvider: PCTokenProvider(url: chatkitInfo.tokenProviderEndpoint),
      userID: chatkitInfo.userId
  )
 
  //Connect to Chatkit and subscribe to a room
9
10
11
12
13
14
15
 chatManager!.connect(delegate: MyChatManagerDelegate()) { (currentUser, error) in
     guard(error == nil) else {
          print("Error connecting: \(error!.localizedDescription)")
          return
     }
     self.currentUser = currentUser
}

Implementing the chatroom

Now that you’ve successfully connected to Chatkit, you can begin implementing the instant messaging feature.

The first thing to do is to subscribe to the room you’ve created. Once you’ve done that, you can start receiving incoming messages through Chatkit.

Handling incoming messages

Before subscribing to a room, you need to be able to handle its events, such as new messages being received. You can handle all room events by implementing the PCRoomDelegate protocol.

Do so by creating an extension for the ChatroomViewController class. To handle incoming messages, you will also need to implement theonMultipartMessage hook:

1
2
3
4
5
extension ChatroomViewController: PCRoomDelegate {
 func onMultipartMessage(_ message: PCMultipartMessage) {
   print("Message received!")
 }
 

Subscribing to a room

Every room your user is a member of is available in the rooms property of the currentUser. For now, your user only should only be a member of a single room, so you can get it like this:

1
let firstRoom = currentUser!.rooms.first!

You can verify that your room’s ID matches the room you created earlier by checking its id property: firstRoom.id.

To subscribe to the room, you need to call the subscribeToRoomMultipartfunction on currentUser, passing it that room:

1
2
3
4
5
6
7
 //Connect to Chatkit and subscribe to a room
 chatManager!.connect(delegate: MyChatManagerDelegate()) { (currentUser, error) in
   guard(error == nil) else {
     print("Error connecting: \(error!.localizedDescription)")
     return
   }
   self.currentUser = currentUser
8
9
10
11
12
13
14
15
16
  let firstRoom = currentUser!.rooms.first!
  // Subscribe to the first room
  currentUser!.subscribeToRoomMultipart(room: firstRoom, roomDelegate: self, completionHandler: { (error) in
    guard error == nil else {
       print("Error subscribing to room: \(error!.localizedDescription)")
       return
    }
    print("Successfully subscribed to the room! 👋")
  })
17
 }

Displaying messages

Lastly, make sure the messages are being stored and displayed in the table as they arrive. Create a variable messages in the ChatroomViewControllerclass that holds an array of PCMultipartMessage:

1
private var messages = [PCMultipartMessage]()

Then, implement displaying messages in the UITableViewDataSourceextension. You will need to replace the empty implementations of the two methods in it:

1
2
3
 //Render messages
 extension ChatroomViewController: UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
4
   return 0
5
   return messages.count
6
7
8
9
  }
 
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
     let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath)
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  
    let message = messages[indexPath.row]
    let sender = message.sender
    var messageText = ""
    
    switch message.parts.first!.payload {
       case .inline(let payload):
         messageText = payload.content
       default:
         print("Message doesn't have the right payload!")
    }
    
    cell.textLabel?.text = sender.displayName
    cell.detailTextLabel?.text = messageText
    if(sender.avatarURL != nil){
        cell.setImageFromUrl(ImageURL: sender.avatarURL!, tableview: tableView)
    }
  
28
29
30
  return cell
  }
 }

The item count you need to return is the size of your messages array.

As each Chatkit message can consist of up to 10 parts of various types (e.g., inline, attachment, and url), you need to take the first part’s payload and assume its type is inlinefor the purposes of this guide.

Read more about handling different message payloads here.

Apart from the payload itself, you’ll likely be interested in who sent the message. For that, you have the sender property, which contains values such as displayName andavatarURL. In the sample above, you have added these to the corresponding cell.

To show the image, we have prepared the setImageFromURL extension method for you. This takes the avatarURL and shows the avatar in the cell’s imageView after fetching the resource from the web.

The last part of handling incoming messages is to insert them into the messages array and the corresponding table view. Modify the PCRoomDelegateextension you created earlier so it will update the table when messages are received:

1
2
3
4
 //Handle incoming message
 extension ChatroomViewController: PCRoomDelegate {
 func onMultipartMessage(_ message: PCMultipartMessage) {
   print("Message received!")
5
6
7
8
   DispatchQueue.main.async {
      self.messages.append(message)
      self.messagesTableView.reloadData()
   }
9
10
 }
}

You may have noticed that the logic for handling messages is inside aDispatchQueue.main.async block. This is because the message handler is running on a background thread. To display it, you need to execute it on the main thread.

By now, you can test receiving messages by running the app in the simulator, which will log in with the user ID specified in Chatkit.plist. You can then send a test message to your chatroom using the instance inspector in the dashboard, which will appear in the list of messages.

Sending messages

To send messages from the app yourself, you need to implement thesendMessage method. It’s called by theonSendClicked action that’s tied to the button click.

1
 func sendMessage(_ message: String) {
2
3
4
5
6
7
8
9
10
11
12
13
14
   self.currentUser!.sendSimpleMessage(
     roomID: self.currentUser!.rooms.first!.id,
     text: message,
     completionHandler: { (messageID, error) in
       guard error == nil else {
         print("Error sending message: \(error!.localizedDescription)")
         return
       }
       DispatchQueue.main.async {
         self.textEntry.text = ""
       }
     }
   )
15
 }

Try running the app and sending a message now.

Extra: Implement the Token Provider

Congratulations! You have successfully implemented Chatkit in an iOS app.
If you recall from earlier - we used the Test Token Provider endpoint we enabled in the dashboard.
That’s a great service for testing, but in production is not particularly secure - anyone requesting a token will be granted one for any user ID that they choose.

In practice, you’ll want to issue your own tokens, and tie them to your existing authentication mechanism.

Change your server/server.js to add the following endpoint:

1
2
3
4
5
  const chatkit = require('./chatkit')
  const Express = require('express')
 
  const app = new Express()
 
6
7
8
9
10
11
 app.post("/auth", (req, res) => {
   const authData = chatkit.authenticate({
     userId: req.query.user_id
   })
   res.status(authData.status).send(authData.body)
 })
12
13
14
15
 
 app.listen(3000, () => {
   console.log("Token providing endpoint listening at: http://localhost:3000/auth")
 })

This will listen for POST requests to http://localhost:3000/auth and issue tokens to it.
At the moment this endpoint does not handle any additional security, but you can easily add it by performing a check with your own backend systems before calling chatkit.authenticate(). A common way to authenticate it would be to take the user ID from the session you created in your application when the user logged in.

You can start the server by running npm run serve-token, and replacing thetokenProvider's url field in your app’sAppDelegate class with your own endpoint -http://localhost:3000/auth.

Note that the localhost URL will only work on a simulated device running on your machine. To test this on a real device, you can use something likeNgrok to create yourself a public URL, or deploy the service to be publicly accessible, for example on Heroku.

Conclusion and next steps

That’s it! You have configured your brand new Chatkit instance and familiarized yourself with the parts that make up Chatkit, including the server and client SDKs, as well as how authentication works.

You can find the complete project in the same repository, in the complete branch - just replace the instance locator and secret key!

For more detailed information on what else is possible, check out the rest of the docs: