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:

Digraph

Overview

  1. ChatMessageLayoutOptionsResolver calculates the ChatMessageLayoutOptions for each message.
  2. ChatMessageLayoutOptions contains all the information needed by the view to render the message.
  3. ChatMessageCell holds the message content view and all the decorations that surround it.
  4. ChatMessageContentView holds the entire message view and all its sub-views.
  5. 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.
  6. ChatReactionsBubbleView is a wrapper for ChatMessageReactionsView.
  7. ChatMessageReactionsView is responsible for rendering all reactions attached to the message.
  8. 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:

BeforeAfter
Message Basic Customization Before
Message Basic Customization 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:

BeforeAfter
Message Basic Resolver Before
Message Basic Resolver 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 EnabledOverlay & Static Enabled
Message List Date Separator Overlay
Message List Date Separator Static

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:
BeforeAfter
Message List Date Separator Static
Message List Date Separator Custom

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 DisabledThread Replies Counter Enabled
Thread List Replies Counter Decoration Disabled
Thread List Replies Counter Decoration 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 DefaultThread Replies Counter Customized
Thread List Replies Counter Decoration Enabled
Thread List Replies Counter Decoration 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:

ChatMessageContentView Layout

  • mainContainer is a horizontal container that holds all top-hierarchy views inside the ChatMessageContentView - This includes the AvatarView, Spacer and BubbleThreadMetaContainer.
  • bubbleThreadMetaContainer is a vertical container that holds the bubbleView at the top and metadataContainer 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 holds authorNameLabel, timestampLabel and onlyVisibleForYouLabel.
  • bubbleView is a view that embeds a bubbleContentContainer and is only responsible for the bubble styling. The bubbleContentContainer contains the textView and quotedMessageView 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:

Custom Message Layout

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:

BeforeAfter
Message Restructure Layout Before
Message Restructure Layout 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:

BeforeAfter
Message Advanced New Button Before
Message Advanced New Button 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.

© Getstream.io, Inc. All Rights Reserved.