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()
}
}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:
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: ContentmentionSymbol
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: BoolisMentionsEnabled
A Boolean value indicating whether the user mentions are enabled.
open var isMentionsEnabled: BoolisAttachmentsEnabled
A Boolean value indicating whether the attachments are enabled.
open var isAttachmentsEnabled: BoolmentionAllAppUsers
When enabled mentions search users across the entire app instead of searching
open private(set) lazy var mentionAllAppUsers: Bool = components.mentionAllAppUsersuserSearchController
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 mentionSuggestercommandSuggester
The component responsible for autocomplete command suggestions.
open lazy var commandSuggestercomposerView
The view of the composer.
open private(set) lazy var composerView: ComposerView = components
.messageComposerView.init()
.withoutAutoresizingMaskConstraintssuggestionsVC
The view controller that shows the suggestions when the user is typing.
open private(set) lazy var suggestionsVC: ChatSuggestionsVCattachmentsVC
The view controller that shows the suggestions when the user is typing.
open private(set) lazy var attachmentsVC: AttachmentsPreviewVCmediaPickerVC
The view controller for selecting image attachments.
open private(set) lazy var mediaPickerVC: UIViewControllerfilePickerVC
The view controller for selecting file attachments.
open private(set) lazy var filePickerVC: UIViewControllerattachmentsPickerActions
Returns actions for attachments picker.
open var attachmentsPickerActions: [UIAlertAction]voiceRecordingVC
The view controller for recording VoiceRecordings.
open private(set) lazy var voiceRecordingVC: VoiceRecordingVCMethods
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) -> UserListQueryThis 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) -> StringshowSuggestions()
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) throwsParameters
url: The URL of the attachmenttype: 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
) -> BoolimagePickerController(_: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:

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()
.withoutAutoresizingMaskConstraintsheaderView
The header view that displays components above the message input view.
public private(set) lazy var headerView = UIView()
.withoutAutoresizingMaskConstraintsbottomContainer
The container that displays the components below the message input view.
public private(set) lazy var bottomContainer = ContainerStackView()
.withoutAutoresizingMaskConstraintscenterContainer
The container that layouts the message input view and the leading/trailing containers around it.
public private(set) lazy var centerContainer = ContainerStackView()
.withoutAutoresizingMaskConstraintsleadingContainer
The container that displays the components in the leading side of the message input view.
public private(set) lazy var leadingContainer = ContainerStackView()
.withoutAutoresizingMaskConstraintstrailingContainer
The container that displays the components in the trailing side of the message input view.
public private(set) lazy var trailingContainer = ContainerStackView()
.withoutAutoresizingMaskConstraintsinputMessageView
A view to input content of the new message.
public private(set) lazy var inputMessageView: InputChatMessageView = components
.inputMessageView.init()
.withoutAutoresizingMaskConstraintssendButton
A button to send the message.
public private(set) lazy var sendButton: UIButton = components
.sendButton.init()
.withoutAutoresizingMaskConstraintsconfirmButton
A button to confirm when editing a message.
public private(set) lazy var confirmButton: UIButton = components
.confirmButton.init()
.withoutAutoresizingMaskConstraintsattachmentButton
A button to open the user attachments.
public private(set) lazy var attachmentButton: UIButton = components
.attachmentButton.init()
.withoutAutoresizingMaskConstraintscommandsButton
A button to open the available commands.
public private(set) lazy var commandsButton: UIButton = components
.commandsButton.init()
.withoutAutoresizingMaskConstraintsshrinkInputButton
A Button for shrinking the input view to allow more space for other actions.
public private(set) lazy var shrinkInputButton: UIButton = components
.shrinkInputButton.init()
.withoutAutoresizingMaskConstraintsdismissButton
A button to dismiss the current state (quoting, editing, etc..).
public private(set) lazy var dismissButton: UIButton = components
.closeButton.init()
.withoutAutoresizingMaskConstraintstitleLabel
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
.withAdjustingFontForContentSizeCategorycheckboxControl
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()
.withoutAutoresizingMaskConstraintsMethods
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 |
|---|---|---|
![]() | ![]() | ![]() |
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.editand populates theeditingMessagewith the provided message.content.quoteMessage(message:): Set’s the state to.quoteand populates thequotingMessage.content.clear(): Set’s the state to.newand 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.
- Composer View Controller
- Usage
- Customization
- Properties
- content
- mentionSymbol
- commandSymbol
- isCommandsEnabled
- isMentionsEnabled
- isAttachmentsEnabled
- mentionAllAppUsers
- userSearchController
- channelController
- channelConfig
- mentionSuggester
- commandSuggester
- composerView
- suggestionsVC
- attachmentsVC
- mediaPickerVC
- filePickerVC
- attachmentsPickerActions
- voiceRecordingVC
- Methods
- setUp()
- setUpLayout()
- viewDidDisappear(_:)
- updateContent()
- setupAttachmentsView()
- publishMessage(sender:)
- showMediaPicker()
- showFilePicker()
- showAttachmentsPicker(sender:)
- shrinkInput(sender:)
- showAvailableCommands(sender:)
- clearContent(sender:)
- createNewMessage(text:)
- editMessage(withId:newText:)
- typingMention(in:)
- typingCommand(in:)
- showCommandSuggestions(for:)
- queryForMentionSuggestionsSearch(typingMention:)
- showMentionSuggestions(for:mentionRange:)
- mentionText(for:)
- showSuggestions()
- dismissSuggestions()
- addAttachmentToContent(from:type:)
- textViewDidChange(_:)
- textView(_:shouldChangeTextIn:replacementText:)
- imagePickerController(_:didFinishPickingMediaWithInfo:)
- documentPicker(_:didPickDocumentsAt:)
- showAttachmentExceedsMaxSizeAlert()
- inputTextView(_:didPasteImage:)
- Composer View
- Methods
- Composer Content


