Message Composer

The Message Composer provides all the UI and necessary functionality for writing and sending messages. It supports sending text, handling chat commands, suggestions autocompletion, uploading attachments like images, files, and videos. The composer is a combination of two components, the ComposerVC and the ComposerView, the first one is a view controller responsible for the functionality, where the latter is only responsible for the UI and layout.

Composer View Controller

The ComposerVC is the view controller that manages all the functionality and interaction with the ComposerView.

Usage

The ComposerVC is used by both the Channel and Thread components, but you can also add the ComposerVC in your View Controller as a child view if needed. Please keep in mind that if you do so you will need to manage the keyboard yourself. Here is an example of how you can add the composer as a child view controller:

class CustomChatVC: UIViewController {

    /// The channel controller injected from the Channel List
    var channelController: ChatChannelController!

    /// Your own custom message list view
    lazy var customMessageListView: CustomMessageListView = CustomMessageListView()

    /// The Message Composer view controller
    lazy var messageComposerVC = ComposerVC()

    /// The bottom constraint of the Message Composer for managing the keyboard
    private var messageComposerBottomConstraint: NSLayoutConstraint?

    /// Component responsible for setting the correct offset when keyboard frame is changed.
    open lazy var keyboardHandler: KeyboardHandler = ComposerKeyboardHandler(
        composerParentVC: self,
        composerBottomConstraint: messageComposerBottomConstraint
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        // Set the required dependencies of the composer
        messageComposerVC.channelController = channelController
        messageComposerVC.userSearchController = ChatClient.shared.userSearchController()

        // Add the message composer as a child view controller
        messageComposerVC.view.translatesAutoresizingMaskIntoConstraints = false
        messageComposerVC.willMove(toParent: self)
        addChild(messageComposerVC)
        view.addSubview(messageComposerVC.view)
        messageComposerVC.didMove(toParent: self)

        // Set the message composer at the bottom of the view
        NSLayoutConstraint.activate([
            messageComposerVC.view.topAnchor.pin(equalTo: customMessageListView.bottomAnchor),
            messageComposerVC.view.leadingAnchor.pin(equalTo: view.leadingAnchor),
            messageComposerVC.view.trailingAnchor.pin(equalTo: view.trailingAnchor)
        ])

        // Message composer bottom constraint to manage the keyboard
        messageComposerBottomConstraint = messageComposerVC.view.bottomAnchor.pin(equalTo: view.bottomAnchor)
        messageComposerBottomConstraint?.isActive = true
    }

    override open func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        keyboardHandler.start()
    }

    override open func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        keyboardHandler.stop()
    }
}

As you can see if you want to use the ComposerVC in your custom message list view you need to setup the dependencies of the composer, add it as a child view controller of your custom message list view controller, and even manage the keyboard yourself or use our keyboard observer to manage it.

Customization

The ComposerVC and ComposerView are highly customizable in both styling and functionality. In case you want to change the styling, adding new views, and new functionality you can take a look at the Customize Message Composer guide. If you want to introduce a new custom attachment and make the composer support it, please read the Message Composer Custom Attachments guide.

Properties

The complete list of all the ComposerVC’s components.

content

The content of the composer.

public var content: Content 

mentionSymbol

A symbol that is used to recognise when the user is mentioning a user.

open var mentionSymbol = "@"

commandSymbol

A symbol that is used to recognise when the user is typing a command.

open var commandSymbol = "/"

isCommandsEnabled

A Boolean value indicating whether the commands are enabled.

open var isCommandsEnabled: Bool 

isMentionsEnabled

A Boolean value indicating whether the user mentions are enabled.

open var isMentionsEnabled: Bool 

isAttachmentsEnabled

A Boolean value indicating whether the attachments are enabled.

open var isAttachmentsEnabled: Bool 

mentionAllAppUsers

When enabled mentions search users across the entire app instead of searching

open private(set) lazy var mentionAllAppUsers: Bool = components.mentionAllAppUsers

userSearchController

A controller to search users and that is used to populate the mention suggestions.

open var userSearchController: ChatUserSearchController!

channelController

A controller that manages the channel that the composer is creating content for.

open var channelController: ChatChannelController?

channelConfig

The channel configuration. If it’s a new channel, an empty configuration should be created. (Not yet supported right now)

public var channelConfig: ChannelConfig? 

mentionSuggester

The component responsible for mention suggestions.

open lazy var mentionSuggester 

commandSuggester

The component responsible for autocomplete command suggestions.

open lazy var commandSuggester 

composerView

The view of the composer.

open private(set) lazy var composerView: ComposerView = components
        .messageComposerView.init()
        .withoutAutoresizingMaskConstraints

suggestionsVC

The view controller that shows the suggestions when the user is typing.

open private(set) lazy var suggestionsVC: ChatSuggestionsVC 

attachmentsVC

The view controller that shows the suggestions when the user is typing.

open private(set) lazy var attachmentsVC: AttachmentsPreviewVC 

mediaPickerVC

The view controller for selecting image attachments.

open private(set) lazy var mediaPickerVC: UIViewController 

filePickerVC

The view controller for selecting file attachments.

open private(set) lazy var filePickerVC: UIViewController 

attachmentsPickerActions

Returns actions for attachments picker.

open var attachmentsPickerActions: [UIAlertAction] 

voiceRecordingVC

The view controller for recording VoiceRecordings.

open private(set) lazy var voiceRecordingVC: VoiceRecordingVC

Methods

setUp()

override open func setUp() 

setUpLayout()

override open func setUpLayout() 

viewDidDisappear(_:)

override open func viewDidDisappear(_ animated: Bool) 

updateContent()

override open func updateContent() 

setupAttachmentsView()

open func setupAttachmentsView() 

publishMessage(sender:)

@objc open func publishMessage(sender: UIButton) 

showMediaPicker()

Shows a photo/media picker.

open func showMediaPicker() 

showFilePicker()

Shows a document picker.

open func showFilePicker() 

showAttachmentsPicker(sender:)

Action that handles tap on attachments button in composer.

@objc open func showAttachmentsPicker(sender: UIButton) 

shrinkInput(sender:)

@objc open func shrinkInput(sender: UIButton) 

showAvailableCommands(sender:)

@objc open func showAvailableCommands(sender: UIButton) 

clearContent(sender:)

@objc open func clearContent(sender: UIButton) 

createNewMessage(text:)

Creates a new message and notifies the delegate that a new message was created.

open func createNewMessage(text: String) 

Parameters

  • text: The text content of the message.

editMessage(withId:newText:)

Updates an existing message.

open func editMessage(withId id: MessageId, newText: String) 

Parameters

  • id: The id of the editing message.
  • newText: The new text content of the message.

typingMention(in:)

Returns a potential user mention in case the user is currently typing a username.

open func typingMention(in textView: UITextView) -> (String, NSRange)? 

Parameters

  • textView: The text view of the message input view where the user is typing.

Returns

A tuple with the potential user mention and the position of the mention so it can be autocompleted.

typingCommand(in:)

Returns a potential command in case the user is currently typing a command.

open func typingCommand(in textView: UITextView) -> String? 

Parameters

  • textView: The text view of the message input view where the user is typing.

Returns

A string of the corresponding potential command.

showCommandSuggestions(for:)

Shows the command suggestions for the potential command the current user is typing.

open func showCommandSuggestions(for typingCommand: String) 

Parameters

  • typingCommand: The potential command that the current user is typing.

queryForMentionSuggestionsSearch(typingMention:)

Returns the query to be used for searching users for the given typing mention.

open func queryForMentionSuggestionsSearch(typingMention term: String) -> UserListQuery 

This function is called in showMentionSuggestions to retrieve the query that will be used to search the users. You should override this if you want to change the user searching logic.

Parameters

  • typingMention: The potential user mention the current user is typing.

Returns

_UserListQuery instance that will be used for searching users.

showMentionSuggestions(for:mentionRange:)

Shows the mention suggestions for the potential mention the current user is typing.

open func showMentionSuggestions(for typingMention: String, mentionRange: NSRange) 

Parameters

  • typingMention: The potential user mention the current user is typing.
  • mentionRange: The position where the current user is typing a mention to it can be replaced with the suggestion.

mentionText(for:)

Provides the mention text for composer text field, when the user selects a mention suggestion.

open func mentionText(for user: ChatUser) -> String 

showSuggestions()

Shows the suggestions view

open func showSuggestions() 

dismissSuggestions()

Dismisses the suggestions view.

open func dismissSuggestions() 

addAttachmentToContent(from:type:)

Creates and adds an attachment from the given URL to the content

open func addAttachmentToContent(from url: URL, type: AttachmentType) throws 

Parameters

  • url: The URL of the attachment
  • type: The type of the attachment

textViewDidChange(_:)

open func textViewDidChange(_ textView: UITextView) 

textView(_:shouldChangeTextIn:replacementText:)

open func textView(
        _ textView: UITextView,
        shouldChangeTextIn range: NSRange,
        replacementText text: String
    ) -> Bool 

imagePickerController(_:didFinishPickingMediaWithInfo:)

open func imagePickerController(
        _ picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
    ) 

documentPicker(_:didPickDocumentsAt:)

open func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) 

showAttachmentExceedsMaxSizeAlert()

open func showAttachmentExceedsMaxSizeAlert() 

inputTextView(_:didPasteImage:)

open func inputTextView(_ inputTextView: InputTextView, didPasteImage image: UIImage)

Composer View

The ComposerView class holds all the composer subviews and implements the composer layout. The composer layout is built with multiple ContainerStackView’s, which are very similar how UIStackView’s work, you can read more about them here. This makes it very customizable since to change the layout you only need to move/remove/add views from different containers.

In the picture below you can see all the containers and main views of the composer:

ComposerVC Documentation

Customization

By default, the ComposerView is managed by the ComposerVC, but if you want to provide your custom view controller to manage the composer view from scratch you can too. The only thing you need to do is to add the composer view to your custom view controller, and then manage all the actions and logic of the composer yourself:

class CustomComposerVC: UIViewController {

    lazy var composerView = ComposerView()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Add the composer view as subview of custom view controller
        view.addSubview(composerView)

        // Setup the composer view constraints to cover all the view
        NSLayoutConstraint.activate([
            composerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            composerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            composerView.topAnchor.constraint(equalTo: view.topAnchor),
            composerView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
}

Properties

Complete list of all the subviews that make the ComposerView.

container

The main container of the composer that layouts all the other containers around the message input view.

public private(set) lazy var container = ContainerStackView()
        .withoutAutoresizingMaskConstraints

headerView

The header view that displays components above the message input view.

public private(set) lazy var headerView = UIView()
        .withoutAutoresizingMaskConstraints

bottomContainer

The container that displays the components below the message input view.

public private(set) lazy var bottomContainer = ContainerStackView()
        .withoutAutoresizingMaskConstraints

centerContainer

The container that layouts the message input view and the leading/trailing containers around it.

public private(set) lazy var centerContainer = ContainerStackView()
        .withoutAutoresizingMaskConstraints

leadingContainer

The container that displays the components in the leading side of the message input view.

public private(set) lazy var leadingContainer = ContainerStackView()
        .withoutAutoresizingMaskConstraints

trailingContainer

The container that displays the components in the trailing side of the message input view.

public private(set) lazy var trailingContainer = ContainerStackView()
        .withoutAutoresizingMaskConstraints

inputMessageView

A view to input content of the new message.

public private(set) lazy var inputMessageView: InputChatMessageView = components
        .inputMessageView.init()
        .withoutAutoresizingMaskConstraints

sendButton

A button to send the message.

public private(set) lazy var sendButton: UIButton = components
        .sendButton.init()
        .withoutAutoresizingMaskConstraints

confirmButton

A button to confirm when editing a message.

public private(set) lazy var confirmButton: UIButton = components
        .confirmButton.init()
        .withoutAutoresizingMaskConstraints

attachmentButton

A button to open the user attachments.

public private(set) lazy var attachmentButton: UIButton = components
        .attachmentButton.init()
        .withoutAutoresizingMaskConstraints

commandsButton

A button to open the available commands.

public private(set) lazy var commandsButton: UIButton = components
        .commandsButton.init()
        .withoutAutoresizingMaskConstraints

shrinkInputButton

A Button for shrinking the input view to allow more space for other actions.

public private(set) lazy var shrinkInputButton: UIButton = components
        .shrinkInputButton.init()
        .withoutAutoresizingMaskConstraints

dismissButton

A button to dismiss the current state (quoting, editing, etc..).

public private(set) lazy var dismissButton: UIButton = components
        .closeButton.init()
        .withoutAutoresizingMaskConstraints

titleLabel

A label part of the header view to display the current state (quoting, editing, etc..).

public private(set) lazy var titleLabel: UILabel = UILabel()
        .withoutAutoresizingMaskConstraints
        .withBidirectionalLanguagesSupport
        .withAdjustingFontForContentSizeCategory

checkboxControl

A checkbox to check/uncheck if the message should also be sent to the channel while replying in a thread.

public private(set) lazy var checkboxControl: CheckboxControl = components
        .checkmarkControl.init()
        .withoutAutoresizingMaskConstraints

Methods

setUpAppearance()

override open func setUpAppearance() 

setUpLayout()

override open func setUpLayout()

Composer Content

The ComposerVC.Content is a struct that contains all the data that will be part of the composed message. It contains the current text of the message, the attachments, the threadMessage in case you are inside a Thread, the command if you are sending, for example, a Giphy, and the state of the composer to determine whether you are creating, editing or quoting a message.

State

The composer has three states: .new, .edit, and .quote. The .new state is when the composer is creating a new message, the .edit state is when we are editing an existing message and changing its content, and finally, the .quote state is when we are replying to a message inline (not in a thread). In the table below we can see the composer in all three different states:

.new.edit.quote
Composer Ui State New
Composer Ui State Edit
Composer Ui State Quote

The .new state is the composer’s default state, and it is initialized by the initial() static function of ComposerVC.Content:

/// The content of the composer. Property of `ComposerVC`.
public var content: Content = .initial() {
    didSet {
        updateContentIfNeeded()
    }
}

You can change the state of the composer through the ComposerVC.Content’s mutating functions:

  • content.editMessage(message:): Set’s the state to .edit and populates the editingMessage with the provided message.
  • content.quoteMessage(message:): Set’s the state to .quote and populates the quotingMessage.
  • content.clear(): Set’s the state to .new and clears all the composer’s content data.

Adding a Command

When adding a command to a message we need to make sure we clean the attachments and the current text. This is why you can only add a command through the ComposerVC.Content’s addCommand(command:) mutating function which does this automatically for you.

Properties

Complete list of all the ComposerVC.Content data and functions.

© Getstream.io, Inc. All Rights Reserved.