class CustomMessageSquaredBubbleView: ChatMessageBubbleView {
override open func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = 0
}
}Message
The responsibility of rendering the messages is shared between multiple components that can be customized or totally replaced.
Here is a diagram that shows the different components that are involved in rendering a message:
Overview
ChatMessageLayoutOptionsResolvercalculates theChatMessageLayoutOptionsfor each message.ChatMessageLayoutOptionscontains all the information needed by the view to render the message.ChatMessageCellholds the message content view and all the decorations that surround it.ChatMessageContentViewholds the entire message view and all its sub-views.ChatMessageBubbleViewwraps the message content inside a bubble. Depending on the layout options, the bubble will have different borders and colors and will show or not the user profile and name.ChatReactionsBubbleViewis a wrapper forChatMessageReactionsView.ChatMessageReactionsViewis responsible for rendering all reactions attached to the message.ChatMessageReactionItemViewrenders a single reaction.
Basic Customizations
In case your application only requires minimal changes to the message view, the SDK makes it easy to apply these changes without much code.
Changing the Bubble View
If you need to customize just one component of the message view, you can easily do it by replacing one of its components with your custom subclass. As an example of how to customize one of the components from the ChatMessageContentView we will replace the bubble view with a custom squared bubble view.
Components.default.messageBubbleView = CustomMessageSquaredBubbleView.selfResult
| Before | After |
|---|---|
![]() | ![]() |
You can find more information on how the components configuration works here.
Simple Layout Changes
The ChatMessageLayoutOptions are flags that the ChatMessageLayoutOptionsResolver injects in each message view depending on the message content (For example: does the message contain reactions? Is it coming from the same user? Etc…). When rendering the message view, the layout options will be used to know which views to show or hide, and if the message cell can be reused since different layout options combinations will produce different reuse identifiers.
By customizing the ChatMessageLayoutOptionsResolver it is possible to do simple layout changes, like for example always showing the timestamp (by default if the messages are sent in the same minute, only the last one shows the timestamp).
final class CustomMessageLayoutOptionsResolver: ChatMessageLayoutOptionsResolver {
override func optionsForMessage(
at indexPath: IndexPath,
in channel: ChatChannel,
with messages: AnyRandomAccessCollection<ChatMessage>,
appearance: Appearance
) -> ChatMessageLayoutOptions {
var options = super.optionsForMessage(at: indexPath, in: channel, with: messages, appearance: appearance)
// Remove the reactions and thread info from each message.
// Remove `.flipped` option, all messages will be rendered in the leading side
// independent if it's the current user or not.
options.remove([
.flipped,
.threadInfo,
.reactions
])
// Always show the avatar, timestamp and author name for each message.
options.insert([.avatar, .timestamp, .authorName])
return options
}
}Components.default.messageLayoutOptionsResolver = CustomMessageLayoutOptionsResolver()Result
| Before | After |
|---|---|
![]() | ![]() |
Decoration Views
Decoration Views are available on SDK version 4.29.0 and above.
The SDK allows you to configure what will be presented above and below your message with decoration views (ChatMessageDecorationView). They are fully customizable, and we also use them for standard SDK components like the Date Separators as seen below.
In order to provide a ChatMessageDecorationView (either a header or a footer) for a message, you will need to implement the following methods from the ChatMessageListVCDelegate
func chatMessageListVC(
_ vc: ChatMessageListVC,
headerViewForMessage message: ChatMessage,
at indexPath: IndexPath
) -> ChatMessageDecorationView?
func chatMessageListVC(
_ vc: ChatMessageListVC,
footerViewForMessage message: ChatMessage,
at indexPath: IndexPath
) -> ChatMessageDecorationView?ChatMessageDecorationView are following a similar flow as the UITableViewHeaderFooterView decorations for sections that UITableView is managing. During the preparation of a message cell, the ChatMessageListVC will ask the delegate to provide ChatMessageDecorationView for header and footer. If the delegate returns a non-nil value the provided ChatMessageDecorationView will be placed in the cell’s UI according to it’s decoration type. In the case where the delegate will return a nil value then the cell will be updated to release the space for this specific decoration type, if it was previously used.
Date Separators
The SDK groups each message from the same day and shows the day which these messages belong to, since by default each message only has the time it was sent, not the day. The StreamChat SDK provides two options out-of-the-box on how to render the grouped messages date separator that can be configured in the Components configuration:
Components.default.messageListDateOverlayEnabled
Components.default.messageListDateSeparatorEnabledBy default only the Components.default.messageListDateOverlayEnabled is enabled, and in this case an overlay at the top will be shown with the day which the current messages being scrolled belong to.
In case you want the separator to also be shown statically between each group of messages, you can enable that option as well, and turn off the overlay option if you so desire:
Components.default.messageListDateSeparatorEnabled = trueResult
| Overlay Enabled | Overlay & Static Enabled |
|---|---|
![]() | ![]() |
You can also easily customize the look of the date separators by subclassing the ChatMessageListDateSeparatorView:
class CustomChatMessageListDateSeparatorView: ChatMessageListDateSeparatorView {
override func setUpAppearance() {
super.setUpAppearance()
backgroundColor = .systemBlue
textLabel.textColor = .black
}
override func updateContent() {
super.updateContent()
textLabel.text = content?.uppercased()
}
override func setUpLayout() {
super.setUpLayout()
NSLayoutConstraint.activate([
textLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 24.0),
textLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -24.0),
textLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
textLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0)
])
}
}
Components.default.messageListDateSeparatorView = CustomChatMessageListDateSeparatorView.selfResult
| Before | After |
|---|---|
![]() | ![]() |
Thread Replies Counter
Thread Replies Counter Decoration View is available on SDK version 4.29.0 and above.
The SDK provides out-of-the-box a decoration view that is being displayed in threads only and shows the number of replies in this thread. You can configure the presentation of the decoration view in the Components configuration:
Components.default.threadRepliesCounterEnabled = true | falseBy default the Components.default.threadRepliesCounterEnabled is enabled, and in this case a decoration view will be displayed just underneath the first(source) message in a thread.
Result
| Thread Replies Counter Disabled | Thread Replies Counter Enabled |
|---|---|
![]() | ![]() |
You can easily customize the look of the thread replies decoration by subclassing the ChatThreadRepliesCountDecorationView:
final class DemoChatThreadRepliesCountDecorationView: ChatThreadRepliesCountDecorationView {
private lazy var leadingLine = UIView()
private lazy var trailingLine = UIView()
override func setUpLayout() {
super.setUpLayout()
let screenScale = UIScreen.main.scale
let hairlineHeight = 1 / screenScale
textLabel.removeFromSuperview()
leadingLine.translatesAutoresizingMaskIntoConstraints = false
textLabel.translatesAutoresizingMaskIntoConstraints = false
trailingLine.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(leadingLine)
container.addSubview(textLabel)
container.addSubview(trailingLine)
NSLayoutConstraint.activate([
leadingLine.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 9),
leadingLine.centerYAnchor.constraint(equalTo: container.centerYAnchor),
leadingLine.heightAnchor.constraint(equalToConstant: hairlineHeight),
textLabel.leadingAnchor.constraint(equalTo: leadingLine.trailingAnchor, constant: 9),
textLabel.topAnchor.constraint(equalTo: container.topAnchor, constant: 3),
textLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -3),
textLabel.centerXAnchor.constraint(equalTo: container.centerXAnchor),
trailingLine.leadingAnchor.constraint(equalTo: textLabel.trailingAnchor, constant: 9),
trailingLine.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -9),
trailingLine.centerYAnchor.constraint(equalTo: container.centerYAnchor),
trailingLine.heightAnchor.constraint(equalToConstant: hairlineHeight)
])
}
override func setUpAppearance() {
super.setUpAppearance()
container.backgroundColor = nil
leadingLine.backgroundColor = UIColor.gray
trailingLine.backgroundColor = UIColor.gray
}
}| Thread Replies Counter Default | Thread Replies Counter Customized |
|---|---|
![]() | ![]() |
Advanced Customizations
Creating a subclass of ChatMessageContentView is the best way to do more advanced customizations since you have access to all the message subviews. By customizing this component you can not only change the existing views, but add new ones and add new functionality.
ChatMessageContentView sets up its own layout on the layout(options: ChatMessageLayoutOptions) method and not in setupLayout() like other regular views. This view is different from the other ones since the layout is calculated based on the ChatMessageLayoutOptions.
Restructuring the Message Layout
In order to change the message layout we first need to understand how it is structured:

mainContaineris a horizontal container that holds all top-hierarchy views inside theChatMessageContentView- This includes theAvatarView,SpacerandBubbleThreadMetaContainer.bubbleThreadMetaContaineris a vertical container that holds thebubbleViewat the top andmetadataContainerat the bottom by default. You can switch the positions for these elements or even add your own according to your needs.metadataContaineris a horizontal container that holdsauthorNameLabel,timestampLabelandonlyVisibleForYouLabel.bubbleViewis a view that embeds abubbleContentContainerand is only responsible for the bubble styling. ThebubbleContentContainercontains thetextViewandquotedMessageViewif the message is a quote.
bubbleView vs bubbleContentContainer
When ChatMessageContentView’s options contain .bubble option, the bubbleView is added to bubbleThreadMetaContainer. If the option is not contained, the hierarchy includes only bubbleContentContainer as subview of bubbleThreadMetaContainer
As an example on how we can restructure the layout of the message view we will do the following customization:

First, we need to customize the ChatMessageLayoutOptionsResolver and change the message layout options according to our needs. For this specific example, let’s assume our message view layout needs to respect the following conditions:
- Always include the avatar, timestamp and author name.
- All messages should be rendered on the leading side.
- Don’t support reactions.
- Don’t support threads.
final class CustomMessageOptionsResolver: ChatMessageLayoutOptionsResolver {
override func optionsForMessage(
at indexPath: IndexPath,
in channel: ChatChannel,
with messages: AnyRandomAccessCollection<ChatMessage>,
appearance: Appearance
) -> ChatMessageLayoutOptions {
// Call super to get the default options.
var options = super.optionsForMessage(at: indexPath, in: channel, with: messages, appearance: appearance)
// Remove all the options that we don't want to support.
// By removing `.flipped` option, all messages will be rendered in the leading side.
options.remove([.flipped, .bubble, .avatarSizePadding, .threadInfo, .reactions])
// Insert the options that we want to support.
options.insert([.avatar, .timestamp, .authorName])
return options
}
}Second, we need to subclass the ChatMessageContentView to restructure the layout. In this case we want to change the position and margins of some views:
final class CustomChatMessageContentView: ChatMessageContentView {
override var maxContentWidthMultiplier: CGFloat { 1 }
override func layout(options: ChatMessageLayoutOptions) {
super.layout(options: options)
// To have the avatarView aligned at the top with rest of the elements,
// we'll need to set the `mainContainer` alignment to leading.
mainContainer.alignment = .leading
// Set inset to zero to align it with the message author
textView?.textContainerInset = .zero
// Reverse the order of the views in the `bubbleThreadMetaContainer`.
// This will reverse the order of the `textView` and `metadataContainer`
let subviews = bubbleThreadMetaContainer.subviews
bubbleThreadMetaContainer.removeAllArrangedSubviews()
bubbleThreadMetaContainer.addArrangedSubviews(subviews.reversed())
// We need to disable the layout margins of the text view
bubbleContentContainer.directionalLayoutMargins = .zero
}
}Finally, don’t forget to assign the custom subclasses to Components:
Components.default.messageLayoutOptionsResolver = CustomMessageOptionsResolver()
Components.default.messageContentView = CustomChatMessageContentView.selfResult
| Before | After |
|---|---|
![]() | ![]() |
Adding new Views and Functionality
To show an example of how to add a new view and functionality to the message view, let’s add a share button whenever the message has attachments, so that we can share or save the attachments to our device.
First, we need to introduce a custom message layout option:
extension ChatMessageLayoutOption {
static let shareAttachments: Self = "shareAttachments"
}The ChatMessageLayoutOption has a similar usage as an enum, but it is not an enum. Instead, it is a struct that holds a string raw value. The advantage of this approach is that it is extendable, while the enum is not.
The next step is to subclass the ChatMessageLayoutOptionsResolver so that we can add the new .shareAttachments option if the message has attachments:
final class CustomMessageLayoutOptionsResolver: ChatMessageLayoutOptionsResolver {
override func optionsForMessage(
at indexPath: IndexPath,
in channel: ChatChannel,
with messages: AnyRandomAccessCollection<ChatMessage>,
appearance: Appearance
) -> ChatMessageLayoutOptions {
var options = super.optionsForMessage(
at: indexPath,
in: channel,
with: messages,
appearance: appearance
)
let messageIndex = messages.index(messages.startIndex, offsetBy: indexPath.item)
let message = messages[messageIndex]
if !message.attachmentCounts.isEmpty {
options.insert(.shareAttachments)
}
return options
}
}Now we need to customize the ChatMessageContentView to add the new functionality in case the new option is present:
final class CustomChatMessageContentView: ChatMessageContentView {
/// The share button.
private var shareAttachmentsButton: UIButton?
/// A callback that is called when the share button is tapped.
/// The message list can then use this callback to present an activity view controller.
var onShareAttachments: (([URL]) -> Void)?
override func layout(options: ChatMessageLayoutOptions) {
super.layout(options: options)
/// We only want to add the share button if the option is present.
if options.contains(.shareAttachments) {
let button = createShareAttachmentsButton()
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 40)
])
/// We want the share button to be rendered in the bottom of the bubble content view.
bubbleContentContainer.spacing = 0
bubbleContentContainer.addArrangedSubview(button)
}
}
/// Creating the share button.
private func createShareAttachmentsButton() -> UIButton {
if shareAttachmentsButton == nil {
shareAttachmentsButton = UIButton(type: .system)
shareAttachmentsButton?.tintColor = .systemBlue
shareAttachmentsButton?.translatesAutoresizingMaskIntoConstraints = false
shareAttachmentsButton?.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal)
shareAttachmentsButton?.addTarget(self, action: #selector(handleTapOnShareButton(_:)), for: .touchUpInside)
}
return shareAttachmentsButton!
}
/// Handling the tap on the share button.
@objc private func handleTapOnShareButton(_ sender: UIButton) {
guard let message = content else { return }
let images = message.imageAttachments.map(\.imageURL)
let files = message.fileAttachments.map(\.assetURL)
let gifs = message.giphyAttachments.map(\.previewURL)
let videos = message.videoAttachments.map(\.videoURL)
let audios = message.audioAttachments.map(\.audioURL)
let links = message.linkAttachments.map(\.originalURL)
let allAttachments = [images, files, gifs, videos, audios, links].reduce([], +)
onShareAttachments?(allAttachments)
}
}Since the ChatMessageContentView is not a view controller it can’t present an UIActivityViewController. So we need to subclass the ChatMessageListVC and handle the onShareAttachments() callback.
class CustomChatMessageListVC: ChatMessageListVC {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath) as! ChatMessageCell
let messageContentView = cell.messageContentView as! CustomChatMessageContentView
messageContentView.onShareAttachments = { [weak self] attachments in
let activityViewController = UIActivityViewController(
activityItems: attachments,
applicationActivities: nil
)
self?.present(activityViewController, animated: true, completion: nil)
}
return cell
}
}Finally, the last step is just to replace these custom components:
Components.default.messageLayoutOptionsResolver = CustomMessageLayoutOptionsResolver()
Components.default.messageContentView = CustomChatMessageContentView.self
Components.default.messageListVC = CustomChatMessageListVC.selfResult
| Before | After |
|---|---|
![]() | ![]() |
ChatMessageContentView
ChatMessageContentView is the container class for a message. Internally this class uses subviews such as the message bubble, reactions, attachments, and user avatars.
Properties and Methods
layoutOptions
The current layout options of the view. When this value is set the subviews are instantiated and laid out just once based on the received options.
public var layoutOptions: ChatMessageLayoutOptions?indexPath
The provider of cell index path which displays the current content view.
public var indexPath: (() -> IndexPath?)?delegate
The delegate responsible for action handling.
public weak var delegate: ChatMessageContentViewDelegate?content
The message this view displays.
open var content: ChatMessage?dateFormatter
The date formatter of the timestampLabel
public lazy var dateFormatter: DateFormattermaxContentWidthMultiplier
Specifies the max possible width of mainContainer.
Should be in [0…1] range, where 1 makes the container fill the entire superview’s width.
open var maxContentWidthMultiplier: CGFloatmessageAuthorAvatarSize
Specifies the size of authorAvatarView. In case .avatarSizePadding option is set the leading offset
for the content will taken from the provided width.
open var messageAuthorAvatarSize: CGSizebubbleView
Shows the bubble around message content.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .bubble.
public private(set) var bubbleView: ChatMessageBubbleView?authorAvatarView
Shows message author avatar.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .author.
public private(set) var authorAvatarView: ChatAvatarView?authorAvatarSpacer
Shows a spacer where the author avatar should be.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .avatarSizePadding.
public private(set) var authorAvatarSpacer: UIView?textView
Shows message text content.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .text.
public private(set) var textView: UITextView?timestampLabel
Shows message timestamp.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .timestamp.
public private(set) var timestampLabel: UILabel?authorNameLabel
Shows message author name.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .authorName.
public private(set) var authorNameLabel: UILabel?onlyVisibleForYouIconImageView
Shows the icon part of the indicator saying the message is visible for current user only.
Exists if layout(options: MessageLayoutOptions) was invoked with the options
containing .onlyVisibleForYouIndicator.
public private(set) var onlyVisibleForYouIconImageView: UIImageView?onlyVisibleForYouLabel
Shows the text part of the indicator saying the message is visible for current user only.
Exists if layout(options: MessageLayoutOptions) was invoked with the options
containing .onlyVisibleForYouIndicator
public private(set) var onlyVisibleForYouLabel: UILabel?errorIndicatorView
Shows error indicator.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .errorIndicator.
public private(set) var errorIndicatorView: ChatMessageErrorIndicator?quotedMessageView
Shows the message quoted by the message this view displays.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .quotedMessage.
public private(set) var quotedMessageView: QuotedChatMessageView?reactionsView
Shows message reactions.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .reactions.
public private(set) var reactionsView: ChatMessageReactionsView?reactionsBubbleView
Shows the bubble around message reactions.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .reactions.
public private(set) var reactionsBubbleView: ChatReactionsBubbleView?threadReplyCountButton
Shows the # of thread replies on the message.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .threadInfo.
public private(set) var threadReplyCountButton: UIButton?threadAvatarView
Shows the avatar of the user who left the latest thread reply.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .threadInfo.
public private(set) var threadAvatarView: ChatAvatarView?threadArrowView
Shows the arrow from message bubble to threadAvatarView view.
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .threadInfo.
public private(set) var threadArrowView: ChatThreadArrowView?attachmentViewInjector
An object responsible for injecting the views needed to display the attachments content.
public private(set) var attachmentViewInjector: AttachmentViewInjector?mainContainer
The root container which holds authorAvatarView (or the avatar padding) and bubbleThreadMetaContainer.
public lazy var mainContainer = ContainerStackView(axis: .horizontal)
.withoutAutoresizingMaskConstraintsbubbleThreadMetaContainer
The container which holds bubbleView (or bubbleContentContainer directly), threadInfoContainer, and metadataView
public private(set) lazy var bubbleThreadMetaContainer = ContainerStackView(axis: .vertical, spacing: 4)
.withoutAutoresizingMaskConstraintsbubbleContentContainer
The container which holds quotedMessageView and textView. It will be added as a subview to bubbleView if it exists
otherwise it will be added to bubbleThreadMetaContainer.
public private(set) lazy var bubbleContentContainer = ContainerStackView(axis: .vertical)
.withoutAutoresizingMaskConstraintsthreadInfoContainer
The container which holds threadArrowView, threadAvatarView, and threadReplyCountButton
public private(set) var threadInfoContainer: ContainerStackView?metadataContainer
The container which holds timestampLabel, authorNameLabel, and onlyVisibleForYouContainer.
Exists if layout(options: MessageLayoutOptions) was invoked with any of
.timestamp/.authorName/.onlyVisibleForYouIndicator options
public private(set) var metadataContainer: ContainerStackView?onlyVisibleForYouContainer
The container which holds onlyVisibleForYouIconImageView and onlyVisibleForYouLabel
public private(set) var onlyVisibleForYouContainer: ContainerStackView?errorIndicatorContainer
The container which holds errorIndicatorView
Exists if layout(options: MessageLayoutOptions) was invoked with the options containing .errorIndicator.
public private(set) var errorIndicatorContainer: UIView?bubbleToReactionsConstraint
Constraint between bubble and reactions.
public private(set) var bubbleToReactionsConstraint: NSLayoutConstraint?Methods
setUpLayoutIfNeeded(options:attachmentViewInjectorType:)
Makes sure the layout(options: ChatMessageLayoutOptions) is called just once.
open func setUpLayoutIfNeeded(
options: ChatMessageLayoutOptions,
attachmentViewInjectorType: AttachmentViewInjector.Type?
)Parameters
options: The options describing the layout of the content view.
layout(options:)
Instantiates the subviews and laid them out based on the received options.
open func layout(options: ChatMessageLayoutOptions)Parameters
options: The options describing the layout of the content view.
updateContent()
override open func updateContent()tintColorDidChange()
override open func tintColorDidChange()handleTapOnErrorIndicator()
Handles tap on errorIndicatorView and forwards the action to the delegate.
@objc open func handleTapOnErrorIndicator()handleTapOnThread()
Handles tap on threadReplyCountButton and forwards the action to the delegate.
@objc open func handleTapOnThread()handleTapOnQuotedMessage()
Handles tap on quotedMessageView and forwards the action to the delegate.
@objc open func handleTapOnQuotedMessage()handleTapOnAvatarView()
Handles tap on avatarView and forwards the action to the delegate.
@objc open func handleTapOnAvatarView()createTextView()
Instantiates, configures and assigns textView when called for the first time.
open func createTextView() -> UITextViewReturns
The textView subview.
createAvatarView()
Instantiates, configures and assigns authorAvatarView when called for the first time.
open func createAvatarView() -> ChatAvatarViewReturns
The authorAvatarView subview.
createAvatarSpacer()
Instantiates, configures and assigns createAvatarSpacer when called for the first time.
open func createAvatarSpacer() -> UIViewReturns
The authorAvatarSpacer subview.
createThreadAvatarView()
Instantiates, configures and assigns threadAvatarView when called for the first time.
open func createThreadAvatarView() -> ChatAvatarViewReturns
The threadAvatarView subview.
createThreadArrowView()
Instantiates, configures and assigns threadArrowView when called for the first time.
open func createThreadArrowView() -> ChatThreadArrowViewReturns
The threadArrowView subview.
createThreadReplyCountButton()
Instantiates, configures and assigns threadReplyCountButton when called for the first time.
open func createThreadReplyCountButton() -> UIButtonReturns
The threadReplyCountButton subview.
createBubbleView()
Instantiates, configures and assigns bubbleView when called for the first time.
open func createBubbleView() -> ChatMessageBubbleViewReturns
The bubbleView subview.
createQuotedMessageView()
Instantiates, configures and assigns quotedMessageView when called for the first time.
open func createQuotedMessageView() -> QuotedChatMessageViewReturns
The quotedMessageView subview.
createReactionsView()
Instantiates, configures and assigns reactionsView when called for the first time.
open func createReactionsView() -> ChatMessageReactionsViewReturns
The reactionsView subview.
createErrorIndicatorView()
Instantiates, configures and assigns errorIndicatorView when called for the first time.
open func createErrorIndicatorView() -> ChatMessageErrorIndicatorReturns
The errorIndicatorView subview.
createErrorIndicatorContainer()
Instantiates, configures and assigns errorIndicatorContainer when called for the first time.
open func createErrorIndicatorContainer() -> UIViewReturns
The errorIndicatorContainer subview.
createReactionsBubbleView()
Instantiates, configures and assigns reactionsBubbleView when called for the first time.
open func createReactionsBubbleView() -> ChatReactionsBubbleViewReturns
The reactionsBubbleView subview.
createTimestampLabel()
Instantiates, configures and assigns timestampLabel when called for the first time.
open func createTimestampLabel() -> UILabelReturns
The timestampLabel subview.
createAuthorNameLabel()
Instantiates, configures and assigns authorNameLabel when called for the first time.
open func createAuthorNameLabel() -> UILabelReturns
The authorNameLabel subview.
createOnlyVisibleForYouIconImageView()
Instantiates, configures and assigns onlyVisibleForYouIconImageView when called for the first time.
open func createOnlyVisibleForYouIconImageView() -> UIImageViewReturns
The onlyVisibleForYouIconImageView subview.
createOnlyVisibleForYouLabel()
Instantiates, configures and assigns onlyVisibleForYouLabel when called for the first time.
open func createOnlyVisibleForYouLabel() -> UILabelReturns
The onlyVisibleToYouLabel subview.
- Overview
- Basic Customizations
- Advanced Customizations
- ChatMessageContentView
- Properties and Methods
- layoutOptions
- indexPath
- delegate
- content
- dateFormatter
- maxContentWidthMultiplier
- messageAuthorAvatarSize
- bubbleView
- authorAvatarView
- authorAvatarSpacer
- textView
- timestampLabel
- authorNameLabel
- onlyVisibleForYouIconImageView
- onlyVisibleForYouLabel
- errorIndicatorView
- quotedMessageView
- reactionsView
- reactionsBubbleView
- threadReplyCountButton
- threadAvatarView
- threadArrowView
- attachmentViewInjector
- mainContainer
- bubbleThreadMetaContainer
- bubbleContentContainer
- threadInfoContainer
- metadataContainer
- onlyVisibleForYouContainer
- errorIndicatorContainer
- bubbleToReactionsConstraint
- Methods
- setUpLayoutIfNeeded(options:attachmentViewInjectorType:)
- layout(options:)
- updateContent()
- tintColorDidChange()
- handleTapOnErrorIndicator()
- handleTapOnThread()
- handleTapOnQuotedMessage()
- handleTapOnAvatarView()
- createTextView()
- createAvatarView()
- createAvatarSpacer()
- createThreadAvatarView()
- createThreadArrowView()
- createThreadReplyCountButton()
- createBubbleView()
- createQuotedMessageView()
- createReactionsView()
- createErrorIndicatorView()
- createErrorIndicatorContainer()
- createReactionsBubbleView()
- createTimestampLabel()
- createAuthorNameLabel()
- createOnlyVisibleForYouIconImageView()
- createOnlyVisibleForYouLabel()













