TUTORIAL

Chat iOS With The Stream Swift SDK

Learn how to use our Chat iOS Swift SDK in this comprehensive step-by-step tutorial.

Ready to get started? We are going to over everything you need to build an iPhone chat app with the Stream iOS SDK written in Swift. At the end of the tutorial, you will have created a fully functional Chat application that you can continue to build on.

Looking for more? We also recently published articles on Stream Chat Push Notifications, Comparing Chat API Pricing; and for feeds please check out our React Native Activity Feeds which details using our Activity Feed React Components. Thanks!

iOS Chat SDK Setup

To get started with the iOS Chat SDK, create a new Swift project in Xcode with a “Single View App” using the project name ChatDemo.

Close the project in Xcode. We will use Cocoapods dependency manager to setup Stream Chat, which will generate Xcode workspace file for our project.

Open a terminal and navigate to your project path:

cd ~/path/to/my/projects/ChatDemo/

CocoaPods

For this tutorial we are going to use CocoaPods as dependency manager. For convenience, we also publish the SDK on Cartage and Swift Package Manager.

If you do not currently have CocoaPods installed, you can do so by running the following command:

sudo gem install cocoapods

Please note that if you already have CocoaPods installed, make sure you run the most recent version or you may receive errors later on in the development process.

sudo gem update cocoapods

Now that you are in your project directory and have ensured that CocoaPods is installed, let’s go ahead and install the Stream Chat SDK.

Initialize CocoaPods and create a Podfile:

pod init

Open the generated Podfile and modify the contents of the file with the following snippet:

platform :ios, '11.0'
inhibit_all_warnings!

target 'ChatDemo' do
  use_frameworks!
  pod 'StreamChat'
end

Now that we’ve modified our Podfile, let’s go ahead and install the project dependencies via the terminal with one simple command:

pod install --repo-update

The above command will generate the ChatDemo.xcworkspace file automatically.

The output should look similar to this:

Analyzing dependencies
Fetching podspec for `StreamChat`
Downloading dependencies
Installing StreamChat (1.0.0)
Installing GzipSwift (5.0.0)
Installing Nuke (7.6.3)
Installing ReachabilitySwift (4.3.1)
Installing RxAppState (1.5.0)
Installing RxCocoa (4.5.0)
Installing RxGesture (2.2.0)
Installing RxKeyboard (0.9.0)
Installing RxReachability (0.1.8)
Installing RxStarscream (0.10)
Installing RxSwift (4.5.0)
Installing SnapKit (5.0.0)
Installing Starscream (3.1.0)
Installing SwiftyGif (5.1.1)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `ChatDemo.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 14 total pods installed.

With our workspace now containing our Pods project with dependencies, as well as our original project, let’s go ahead and move over to Xcode to complete the process.

Add Stream Chat to your iOS application

Open ChatDemo.xcworkspace in Xcode.

To setup Stream Chat we need to add a couple things in your AppDelegate.swift file. Here is what your AppDelegate file should look like:

import UIKit
import StreamChat

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Client.config = .init(apiKey: "b67pax5b2wdq")
        Client.shared.set(user: User(id: "noisy-hat-9",
                                      name: "Noisy hat"),
                          token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoibm9pc3ktaGF0LTkifQ.3KQERxv8_hpIGeWY6mtvuOcl-jKlIg-at-xvT4SzNyA")
        return true
    }
}

Your app is now configured with the API credentials for the demo app.

Note that Client.shared is a singleton we define in app delegate and re-use throughout the entire application. The shared client manages current user's session (token) and your Stream application API credentials (apiKey).

Because this is a tutorial, we include pre-generated a user token. In a real-world application, your authentication backend would generate such token at log-in / signup and hand it over to the mobile app. More information about this is available on the Chat API docs.

Add Single Conversation Screen

Adding chat is simple as the library comes with a built-in ChatViewController class which loads messages for a specified channel using the APIs and renders its content.

Because we started this project as a “Single View App” we already have a ViewController in our project, but we don't need it and will use ChatViewController directly.

For the demo we will use Storyboard to set up ChatViewController, but you can use it in code too.

  1. Select Main.storyboard file in Navigator.
  2. Select View Controller and open the inspector tab with the Identity Inspector.
  3. In section Custom Class change the current value of ViewController to ChatViewController and Module value to the StreamChat.

Your editor should now look like this screenshot:

We are almost done for the first launch. The last thing we need to do is to set up a channel for ChatViewController. Go back to AppDelegate and add the following new lines to it:

import UIKit
import StreamChat

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Client.config = .init(apiKey: "b67pax5b2wdq")
        Client.shared.set(user: User(id: "noisy-hat-9",
                                      name: "Noisy hat"),
                          token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoibm9pc3ktaGF0LTkifQ.3KQERxv8_hpIGeWY6mtvuOcl-jKlIg-at-xvT4SzNyA")

        if let chatViewController = window?.rootViewController as? ChatViewController {
            let channel = Channel(id: "general", name: "general")
            chatViewController.channelPresenter = ChannelPresenter(channel: channel)
        }
        return true
    }
}

Note: for security and privacy reasons, iOS does not allow apps to access your files unless you add some code to support that. We will show you later how to set that up, just keep in mind that image upload and camera upload won’t work just yet ;)

Now we’re ready to run our application. It’s now showing messages from the General channel when you try to send a message.

Message Attachments

In order to attach images or camera pictures to messages, your app needs to request access from the user. Once you do that, you will be able to pick files and images and add them to messages.

You have to make a few changes to the Info.plist file.

  • NSPhotoLibraryUsageDescription — will give access to the user photo library,
  • NSCameraUsageDescription — will give access to the camera for taking photos,
  • NSMicrophoneUsageDescription — will give access to the microphone for taking video from the camera.
...
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) would like to access your camera</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) would like to access your microphone</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) would like to access your photo</string>
...

Chat Features

Did you already try to post a message like this? /giphy awesome

Stream Chat comes with out-of-the-box features such as:

  1. Commands: focus on the composer and type / to use commands, like /giphy
  2. Reactions: tap on a message to add reactions or long press on images
  3. Link preview will be generated automatically when you send a link.
  4. Attach images: use the plus button ⨁ in the composer to attach images or files.
  5. Edit message: long press on your own messages to open the menu with the Edit button.
  6. Typing events: you’ll see typing events at the bottom of messages when someone starts typing.
  7. Threads: you can create threads to any message in a channel, just tap on a message and hit Reply to enter the thread view.

Some of the features are hard to see in action with just one user online. If you are interested, you can open the same channel on the web and try user to user interactions like typing events, reactions and threads.

Multiple Conversations

Most chat applications handle more than just one single conversation. Facebook Messenger, Whatsapp and Telegram all allow you to have multiple one-to-one and group conversations.

Let’s see how we can change our application so that we first see the list of channels.

  1. Remove these single chat setup lines from AppDelegate.swift: we used this to connect to one hard-coded channel so this is not needed anymore.

    if let chatViewController = window?.rootViewController as? ChatViewController {
        let channel = Channel(id: "general", name: "general")
        chatViewController.channelPresenter = ChannelPresenter(channel: channel)
    }

    And replace it with the following lines:

    if let navigationController = window?.rootViewController as? UINavigationController,
        let channelsViewController = navigationController.viewControllers.first as? ChannelsViewController {
        channelsViewController.channelsPresenter.filter = .key("members", .in(["noisy-hat-9))
    }

    We want to show all channels that have the current user as a member; the ChannelsViewController supports custom filters. In this case we are going to use .key(.members, .in(["noisy-hat-9"])). More information about this is available on the docs

    Your App.delegate.swift should now look like this:

    import UIKit
    import StreamChat
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            Client.config = .init(apiKey: "b67pax5b2wdq")
            Client.shared.set(user: User(id: "noisy-hat-9",
                                          name: "Noisy hat"),
                              token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoibm9pc3ktaGF0LTkifQ.3KQERxv8_hpIGeWY6mtvuOcl-jKlIg-at-xvT4SzNyA")
    
                if let navigationController = window?.rootViewController as? UINavigationController,
                    let channelsViewController = navigationController.viewControllers.first as? ChannelsViewController {
                    channelsViewController.channelsPresenter.filter = .key("members", .in(["noisy-hat-9"]))
                }
                return true
        }
    }
  2. Open Main.storyboard and change the class name in the inspector section Custom class to ChannelsViewController.
  3. Open Xcode menu Editor > Embed In > Navigation Controller. (Make sure that the View Controller is selected in the storyboard or you won’t be able to )

That’s it! If you restart the application it will show the list of channels your user has access to. The channel list supports pagination out of the box and keeps channels’ order synchronized automatically.

Not all applications have the same logic when it comes to listing channels. Both the APIs and SDK allow you to provide your own filtering and ordering parameters.

You can find more about how the list of channels can be filtered and ordered in the API docs and on the SDK docs.

Customize Channel Preview

So far you’ve learned how to use the default components. The library has been designed to be fully extendable, enabling you to build any type of chat. In particular we aim to support these 5 use cases:

  • Social/Messaging chat
  • Team/Slack style chat
  • Customer support chat
  • Livestream chat
  • Gaming

Let’s see how we can make some changes to the SDK’s UI components. We will start by changing how channel previews are shown in the channel list and include the number of unread messages for each.

ChannelsViewController renders each channel preview as a table cell;, all we need to do here is to subclass it and override channelCell(at indexPath: IndexPath, channelPresenter: ChannelPresenter) and include our unread count there.

  1. Open ViewController.swift and change it to this:

    import UIKit
    // Add Stream Chat SDK.
    import StreamChat
    
    /// Use ChannelsViewController as the parent view controller.
    class ViewController: ChannelsViewController {
        /// We override the inherited method for a channel cell.
        /// Here we can create absolutely new table view cell for the channel.
        override func channelCell(at indexPath: IndexPath, channelPresenter: ChannelPresenter) -> UITableViewCell {
            // For now, get the default channel cell.
            let cell = super.channelCell(at: indexPath, channelPresenter: channelPresenter)
    
            // We need to check if the returned cell is ChannelTableViewCell.
            guard let channelCell = cell as? ChannelTableViewCell else {
                return cell
            }
    
            // Check the number of unread messages.
            if channelPresenter.unreadCount > 0 {
                // Add the info about unread messages to the cell.
                channelCell.update(info: "\(channelPresenter.unreadCount) unread", bold: true)
            }
    
            return channelCell
        }
    }
  2. Open Main.storyboard and make sure to change class and module to be like this:

Now we can run the app and see two states of the channel cell:

Press the button below to send yourself a message, you can see how the unread count changes in real-time.

Custom Message

Let’s move to the chat screen and make custom UI for messages. Let’s create another view controller for that:

  • Select ChatDemo folder in the File inspector where we’ll create a new view controller.
  • Select in the File menu New > File...
  • Choose the Cocoa Touch Class template
  • Type the name of the class as DemoChatViewController
  • Type the subclass as ChatViewController (it’s from Stream Chat SDK)
  • Press next and create the file
  • Change the code of the DemoChatViewController.swift to this:
import UIKit
import StreamChat

class DemoChatViewController: ChatViewController {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        // Setup the general channel for the chat.
        let channel = Channel(id: "general", name: "general")
        channelPresenter = ChannelPresenter(channel: channel)
    }
    /// Override the default implementation of UI messages
    /// with default UIKit table view cell.
    override func messageCell(at indexPath: IndexPath, message: Message,    readUsers: [User]) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "message")
            ?? UITableViewCell(style: .value2, reuseIdentifier: "message")

        cell.textLabel?.text = message.user.name
        cell.textLabel?.font = .systemFont(ofSize: 12, weight: .bold)
        cell.detailTextLabel?.text = message.text
        cell.detailTextLabel?.numberOfLines = 0

        return cell
    }
}

So in the code above:

  1. We updated the init method to setup the channel presenter. For the demo we use the general channel.
  2. We override the messageCell method to implement own presentation for messages. For the demo the default UITableViewCell with style value2 would be great.
  3. cell.textLablel was updated for a user’s name.
  4. cell.detailTextLabel was updated for a message.

The last step before to run our app we need to say to Main.storyboard use it instead of ViewController with channel list:

  1. Open Main.storyboard
  2. Select ViewController and change the class name to DemoChatViewController.

Run the app, type some messages and we'll see a simple chat with default UIKit table view cell.

Additionally you can add a customization for the loading and status cells. Please check the docs here.

Custom Theme

Replacing the default UI implementation with own is great, but it required some time to develop. What if we only need to change some colors, fonts and borders? It’s easy to do with styles objects. Stream Chat provide light and dark themes. The light theme is the default.

Let’s change styles for in the last our DemoChatViewController. But before that we should move back to the default UI implementation of messages. Just remove the function messageCell.

You should setup the style property of chat or channels before showing them or setup it in some init method. Let’s update the DemoChatViewController we created last step and replace it's contents with the following code:

import UIKit
import StreamChat

class DemoChatViewController: ChatViewController {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        // Set the general channel.
        let channel = Channel(id: "general", name: "General")
        channelPresenter = ChannelPresenter(channel: channel)

        // Update styles.
        style.incomingMessage.chatBackgroundColor = UIColor(hue: 0.2, saturation: 0.3, brightness: 1, alpha: 1)
        style.incomingMessage.backgroundColor = UIColor(hue: 0.6, saturation: 0.5, brightness: 1, alpha: 1)
        style.incomingMessage.borderWidth = 0

        style.outgoingMessage.chatBackgroundColor = style.incomingMessage.chatBackgroundColor
        style.outgoingMessage.backgroundColor = style.incomingMessage.chatBackgroundColor
        style.outgoingMessage.font = .systemFont(ofSize: 15, weight: .bold)
        style.outgoingMessage.cornerRadius = 0
        style.outgoingMessage.showCurrentUserAvatar = false
    }
}

We changed several properties of incomingMessage and outgoingMessage styles.

iOS/Swift Chat Tutorial – Final thoughts

In this Chat App tutorial we learned how to build a fully functional chat app using Swift. We also showed how easy it is to customize the behavior and build any type of chat or messaging experience.

The underlying chat API is based on Go, RocksDB and Raft. This makes the chat experience extremely fast with response times that are often below 10ms. Stream powers activity feeds and chat for over 500 million end users, so you can feel confident about our APIs.

Both the Swift Chat SDK and the Chat API have plenty more features available to support more advanced use-cases such as push notifications, content moderation, rich messages and more.

Next Steps