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
ChatMessageLayoutOptionsResolver
calculates theChatMessageLayoutOptions
for each message.ChatMessageLayoutOptions
contains all the information needed by the view to render the message.ChatMessageCell
holds the message content view and all the decorations that surround it.ChatMessageContentView
holds the entire message view and all its sub-views.ChatMessageBubbleView
wraps 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.ChatReactionsBubbleView
is a wrapper forChatMessageReactionsView
.ChatMessageReactionsView
is responsible for rendering all reactions attached to the message.ChatMessageReactionItemView
renders 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.
class CustomMessageSquaredBubbleView: ChatMessageBubbleView {
override open func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = 0
}
}
Components.default.messageBubbleView = CustomMessageSquaredBubbleView.self
Result:
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.messageListDateSeparatorEnabled
By 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 = true
Result:
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.self
Result:
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 | false
By 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:
mainContainer
is a horizontal container that holds all top-hierarchy views inside theChatMessageContentView
- This includes theAvatarView
,Spacer
andBubbleThreadMetaContainer
.bubbleThreadMetaContainer
is a vertical container that holds thebubbleView
at the top andmetadataContainer
at the bottom by default. You can switch the positions for these elements or even add your own according to your needs.metadataContainer
is a horizontal container that holdsauthorNameLabel
,timestampLabel
andonlyVisibleForYouLabel
.bubbleView
is a view that embeds abubbleContentContainer
and is only responsible for the bubble styling. ThebubbleContentContainer
contains thetextView
andquotedMessageView
if 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.self
Result:
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.self
Result:
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: DateFormatter
maxContentWidthMultiplier
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: CGFloat
messageAuthorAvatarSize
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: CGSize
bubbleView
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)
.withoutAutoresizingMaskConstraints
bubbleThreadMetaContainer
The container which holds bubbleView
(or bubbleContentContainer
directly), threadInfoContainer
, and metadataView
public private(set) lazy var bubbleThreadMetaContainer = ContainerStackView(axis: .vertical, spacing: 4)
.withoutAutoresizingMaskConstraints
bubbleContentContainer
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)
.withoutAutoresizingMaskConstraints
threadInfoContainer
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() -> UITextView
Returns
The textView
subview.
createAvatarView()
Instantiates, configures and assigns authorAvatarView
when called for the first time.
open func createAvatarView() -> ChatAvatarView
Returns
The authorAvatarView
subview.
createAvatarSpacer()
Instantiates, configures and assigns createAvatarSpacer
when called for the first time.
open func createAvatarSpacer() -> UIView
Returns
The authorAvatarSpacer
subview.
createThreadAvatarView()
Instantiates, configures and assigns threadAvatarView
when called for the first time.
open func createThreadAvatarView() -> ChatAvatarView
Returns
The threadAvatarView
subview.
createThreadArrowView()
Instantiates, configures and assigns threadArrowView
when called for the first time.
open func createThreadArrowView() -> ChatThreadArrowView
Returns
The threadArrowView
subview.
createThreadReplyCountButton()
Instantiates, configures and assigns threadReplyCountButton
when called for the first time.
open func createThreadReplyCountButton() -> UIButton
Returns
The threadReplyCountButton
subview.
createBubbleView()
Instantiates, configures and assigns bubbleView
when called for the first time.
open func createBubbleView() -> ChatMessageBubbleView
Returns
The bubbleView
subview.
createQuotedMessageView()
Instantiates, configures and assigns quotedMessageView
when called for the first time.
open func createQuotedMessageView() -> QuotedChatMessageView
Returns
The quotedMessageView
subview.
createReactionsView()
Instantiates, configures and assigns reactionsView
when called for the first time.
open func createReactionsView() -> ChatMessageReactionsView
Returns
The reactionsView
subview.
createErrorIndicatorView()
Instantiates, configures and assigns errorIndicatorView
when called for the first time.
open func createErrorIndicatorView() -> ChatMessageErrorIndicator
Returns
The errorIndicatorView
subview.
createErrorIndicatorContainer()
Instantiates, configures and assigns errorIndicatorContainer
when called for the first time.
open func createErrorIndicatorContainer() -> UIView
Returns
The errorIndicatorContainer
subview.
createReactionsBubbleView()
Instantiates, configures and assigns reactionsBubbleView
when called for the first time.
open func createReactionsBubbleView() -> ChatReactionsBubbleView
Returns
The reactionsBubbleView
subview.
createTimestampLabel()
Instantiates, configures and assigns timestampLabel
when called for the first time.
open func createTimestampLabel() -> UILabel
Returns
The timestampLabel
subview.
createAuthorNameLabel()
Instantiates, configures and assigns authorNameLabel
when called for the first time.
open func createAuthorNameLabel() -> UILabel
Returns
The authorNameLabel
subview.
createOnlyVisibleForYouIconImageView()
Instantiates, configures and assigns onlyVisibleForYouIconImageView
when called for the first time.
open func createOnlyVisibleForYouIconImageView() -> UIImageView
Returns
The onlyVisibleForYouIconImageView
subview.
createOnlyVisibleForYouLabel()
Instantiates, configures and assigns onlyVisibleForYouLabel
when called for the first time.
open func createOnlyVisibleForYouLabel() -> UILabel
Returns
The onlyVisibleToYouLabel
subview.