How to customize in-app chat on iOS with the Sendbird UIKit
Introduction
This tutorial will show you how to customize the iOS Sendbird UIKit for chat. We will customize two components:
- The channel list view
- The message list view
Before we start, you will need to install the Sendbird UIKit. To do so, follow the instructions in the Sendbird docs.
Now that you are ready, let’s dive right into it.
How to customize the Chat Channel List
To change the aspect of the Channel List, we need to update the SBUChannelListViewController. The gist below contains the code to customize:
First, let’s:
- Initialize the UIKit Channel List View Controller
- And override its functions and properties
This is the same process to customize any component of the Sendbird Chat UIKit for iOS.
Below is a basic pattern to initialize a custom UIKit Channel List View Class. The IBAction openChat is a button in the view controller that displays the UIKit channel list.
1 | import UIKit |
2 | import SendBirdUIKit |
3 | |
4 | class ChannelListVC: SBUChannelListViewController {} |
5 | |
6 | class ViewController: UIViewController { |
7 | |
8 | @IBAction func openChat(_ sender: UIButton) { |
9 | |
10 | let channelListVC = ChannelListVC() |
11 | let naviVC = UINavigationController(rootViewController: channelListVC) |
12 | naviVC.modalPresentationStyle = .fullScreen |
13 | present(naviVC, animated: true) |
14 | } |
15 | } |
Now, let’s change the look of the:
- leftBarButton
- titleView
- rightBarButton
- channelCell
- Custom channel list query

Customizing the leftBarButton
To customize the leftBarButton use this code:
import UIKit | |
import SendBirdUIKit | |
class ChannelListVC: SBUChannelListViewController { | |
override init() { | |
super.init() | |
self.leftBarButton = self.createHighlightedBackButton() | |
} | |
func createHighlightedBackButton() -> UIBarButtonItem { | |
return UIBarButtonItem(title: "back", style: .plain, target: self, action: | |
#selector(onClickBack)) | |
} | |
@objc func onClickBack() { | |
self.navigationController?.popViewController(animated: true) | |
self.dismiss(animated: true, completion: nil) | |
} | |
} |
Customizing the titleView
To customize the titleView, use this code:
import UIKit | |
import SendBirdUIKit | |
class ChannelListVC: SBUChannelListViewController { | |
override init() { | |
super.init() | |
self.titleView = self.createCustomTitleLabel() | |
} | |
func createCustomTitleLabel() -> UILabel { | |
let titleLabel = UILabel() | |
titleLabel.text = "Your Chat List" | |
return titleLabel | |
} | |
} |
Customizing the channelListVC.rightBarButton
To customize the channelListVC.rightBarButton use this code:
override init() { | |
super.init() | |
self.rightBarButton?.isEnabled = false | |
} |
Customizing the channelListVC.register(channelCell: CustomChannelListCell())
To customize the channelListVC.register(channelCell: CustomChannelListCell()), use this code:
override init() { | |
#if swift(>=5.2) | |
self.register(channelCell: CustomChannelListCell()) | |
#else | |
self.register(channelCell: CustomChannelListCell( style: .default, | |
reuseIdentifier: CustomChannelListCell.sbu_className) | |
) | |
#endif | |
} |
For the CustomChannelListCell class, the code focuses on the programmatic rendering of cell views. Here is a working example of the code:
// | |
// CustomChannelListCell.swift | |
// SendBirdUIKit-Sample | |
// | |
// Copyright © 2020 SendBird, Inc. All rights reserved. | |
// | |
import UIKit | |
import SendBirdUIKit | |
class CustomChannelListCell: SBUBaseChannelCell { | |
// MARK: - Properties | |
@SBUAutoLayout var coverImage = UIImageView() | |
@SBUAutoLayout var separatorLine = UIView() | |
@SBUAutoLayout var titleLabel = UILabel() | |
@SBUAutoLayout var memberLabel = UILabel() | |
@SBUAutoLayout var titleStackView: UIStackView = { | |
let titleStackView = UIStackView() | |
titleStackView.alignment = .center | |
titleStackView.spacing = 4.0 | |
titleStackView.axis = .horizontal | |
return titleStackView | |
}() | |
let kCoverImageSize: CGFloat = 40 | |
// MARK: - | |
override func setupViews() { | |
super.setupViews() | |
self.coverImage.clipsToBounds = true | |
self.coverImage.frame = CGRect(x: 0, y: 0, width: kCoverImageSize, height: kCoverImageSize) | |
self.contentView.addSubview(self.coverImage) | |
self.titleStackView.addArrangedSubview(self.titleLabel) | |
// Not yet available | |
self.titleStackView.addArrangedSubview(self.memberLabel) | |
self.contentView.addSubview(self.titleStackView) | |
self.contentView.addSubview(self.separatorLine) | |
self.titleLabel.font = SBUTheme.channelCellTheme.titleFont | |
self.titleLabel.textColor = SBUTheme.channelCellTheme.titleTextColor | |
self.separatorLine.backgroundColor = SBUTheme.channelCellTheme.separatorLineColor | |
self.memberLabel.textColor = .gray | |
} | |
override func setupAutolayout() { | |
super.setupAutolayout() | |
NSLayoutConstraint.activate([ | |
self.coverImage.topAnchor.constraint( | |
equalTo: self.contentView.topAnchor, | |
constant: 10 | |
), | |
self.coverImage.bottomAnchor.constraint( | |
equalTo: self.contentView.bottomAnchor, | |
constant: -10 | |
), | |
self.coverImage.leadingAnchor.constraint( | |
equalTo: self.contentView.leadingAnchor, | |
constant: 16 | |
), | |
self.coverImage.widthAnchor.constraint(equalToConstant: kCoverImageSize), | |
self.coverImage.heightAnchor.constraint(equalToConstant: kCoverImageSize), | |
]) | |
NSLayoutConstraint.activate([ | |
self.titleStackView.topAnchor.constraint( | |
equalTo: self.contentView.topAnchor, | |
constant: 10 | |
), | |
self.titleStackView.bottomAnchor.constraint( | |
equalTo: self.contentView.bottomAnchor, | |
constant: -10 | |
), | |
self.titleStackView.leadingAnchor.constraint( | |
equalTo: self.coverImage.trailingAnchor, | |
constant: 16 | |
), | |
self.titleStackView.rightAnchor.constraint( | |
equalTo: self.contentView.rightAnchor, | |
constant: -16), | |
]) | |
self.titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) | |
self.memberLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) | |
NSLayoutConstraint.activate([ | |
// From bottom of cell | |
self.separatorLine.topAnchor.constraint( | |
equalTo: self.coverImage.bottomAnchor, | |
constant: -0.5 | |
), | |
// From left side | |
self.separatorLine.leadingAnchor.constraint( | |
equalTo: self.titleStackView.leadingAnchor, | |
constant: 0 | |
), | |
// From right side | |
self.separatorLine.trailingAnchor.constraint( | |
equalTo: self.contentView.trailingAnchor, | |
constant: -16 | |
), | |
//Line Width | |
self.separatorLine.heightAnchor.constraint(equalToConstant: 10.0) | |
]) | |
} | |
override func configure(channel: SBDBaseChannel) { | |
super.configure(channel: channel) | |
self.titleLabel.text = channel.name.count > 0 ? channel.name : "Empty channel" | |
let ch = channel as! SBDGroupChannel | |
if let message = ch.lastMessage?.message { | |
self.memberLabel.text = "'\(message)'" | |
} | |
self.coverImage.image = UIImage(named: "kitten") | |
self.coverImage.layer.cornerRadius = kCoverImageSize/2 | |
self.coverImage.layer.masksToBounds = true | |
} | |
} |
Customizing the channel list query
- When creating the channel list view, include your custom query during initialization, and pass it into the super.init() method.
- The custom channel list query leverages the Sendbird core SDK’s channel list query. The Sendbird core SDK is part of the Sendbird UIKit library.
class ChannelListVC: SBUChannelListViewController { | |
override init(channelListQuery: SBDGroupChannelListQuery? = nil) { | |
super.init(channelListQuery: channelListQuery) | |
} | |
} | |
class ViewController: UIViewController { | |
@IBAction func openChat(_ sender: UIButton) { | |
let listQuery = SBDGroupChannel.createMyGroupChannelListQuery() | |
listQuery?.includeEmptyChannel = true | |
listQuery?.includeFrozenChannel = true | |
// ... You can set more query options | |
let channelListVC = ChannelListVC(channelListQuery: listQuery) | |
let naviVC = UINavigationController(rootViewController: channelListVC) | |
naviVC.modalPresentationStyle = .fullScreen | |
present(naviVC, animated: true) | |
} | |
} |
How to customize the channel message list
This viewController shows all the messages in a particular channel. In this example, we will change the:
- leftBarButton
- titleView
- rightBarButton
- register(channelCell: CustomUserMessageCell())
- Customize message send

You will need to initialize your channelViewController with either a channel URL or a channel object to customize a single channel. The code below demonstrates overriding the SBUChannelListViewController’s didSelectRowAt delegate. The override allows you to listen to the channel selected within the channelListViewController then display the channelViewController.
import UIKit | |
import SendBirdUIKit | |
class MessageList: SBUChannelViewController { | |
init(channelUrl: String){ | |
super.init(channelUrl: channelUrl) | |
} | |
} | |
class ChannelListVC: SBUChannelListViewController { | |
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { | |
let channel = self.channelList[indexPath.row].channelUrl | |
let channelVC = MessageList(channelUrl: channel) | |
let naviVC = UINavigationController(rootViewController: channelVC) | |
naviVC.modalPresentationStyle = .fullScreen | |
self.present(naviVC, animated: true) | |
} | |
} | |
class ViewController: UIViewController, UITableViewDelegate { | |
@IBAction func openChannelList(_ sender: UIButton) { | |
let channelListVC = ChannelListVC() | |
let naviVC = UINavigationController(rootViewController: channelListVC) | |
naviVC.modalPresentationStyle = .fullScreen | |
self.present(naviVC, animated: true) | |
} | |
} |
Customizing the leftBarButton
To customize the leftBarButton use this code:
class MessageList: SBUChannelViewController { | |
init(channelUrl: String){ | |
super.init(channelUrl: channelUrl) | |
self.leftBarButton = createHighlightedBackButton() | |
} | |
func createHighlightedBackButton() -> UIBarButtonItem { | |
let backButton = UIButton(type: .custom) | |
backButton.frame = .init(x: 0, y: 0, width: 50, height: 45) | |
backButton.setTitle("Back", for: .normal) | |
backButton.setTitleColor(SBUColorSet.primary300, for: .normal) | |
backButton.addTarget(self, action: #selector(clickedBack), for: .touchUpInside) | |
let backBarButton = UIBarButtonItem(customView: backButton) | |
return backBarButton | |
} | |
@objc func clickedBack() { | |
self.navigationController?.popViewController(animated: true) | |
self.dismiss(animated: true, completion: nil) | |
} | |
} |
Customizing the titleView
To customize the titleView, use this code:
class MessageList: SBUChannelViewController { | |
init(channelUrl: String){ | |
super.init(channelUrl: channelUrl) | |
let myView = UILabel() | |
myView.text = "My Message" | |
self.titleView = myView | |
} | |
} |
Customizing the rightBarButton
To customize the rightBarButton use this code:
class MessageList: SBUChannelViewController { | |
init(channelUrl: String){ | |
super.init(channelUrl: channelUrl) | |
self.rightBarButton? = UIBarButtonItem(title: "test", style: .done, target: self, action: #selector(addTapped)) | |
} | |
@objc func addTapped () { | |
print("tapped") | |
} | |
} |
Customizing the register(channelCell:CustomUserMessageCell())
To customize the register(channelCell:CustomUserMessageCell()) use this code:
class CustomUserMessageCell: SBUUserMessageCell { | |
override func setupStyles() { | |
super.setupStyles() | |
self.messageTextView.backgroundColor = .red | |
self.messageContentView.backgroundColor = .lightGray | |
} | |
} | |
class MessageList: SBUChannelViewController { | |
init(channelUrl: String){ | |
super.init(channelUrl: channelUrl) | |
self.register(userMessageCell: CustomUserMessageCell()) | |
} | |
} |
Customizing message send
To customize message send, use this code:
override func messageInputView(_ messageInputView: SBUMessageInputView, didSelectSend text: String) { | |
guard text.count > 0 else { return } | |
guard let messageParams = SBDUserMessageParams(message: text) else { | |
return | |
} | |
messageParams.customType = "EXAMPLE 1" | |
self.sendUserMessage(messageParams: messageParams) | |
print("Working") | |
} |
Color set
By default, Sendbird provides a beautiful user interface with light or dark themes. To further customize and specify the exact colors to be used with both light and dark themes, you can use our UIKit color set guide. This document will help you set harmonious colors and contrast levels between the backgrounds and chat UI components.
Conclusion
This tutorial walked you through the steps to customize two components of the iOS Sendbird UIKit for Chat. For additional UI modifications, check the docs for the complete list of customizable themes and how to change common resources such as Strings, Icons, and other Styles.
You are on your way to creating a beautiful and personalized chat experience! Keep it going and happy chat building! 🙂