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: 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 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
) -> 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:
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 |
---|---|---|
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 theeditingMessage
with the provided message.content.quoteMessage(message:)
: Set’s the state to.quote
and populates thequotingMessage
.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.
- 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