Draft Messages

The UIKit SDK provides a way to create and manage draft messages.

Drafts are disabled by default. In order to enable this feature, you need to enable the Components.default.isDraftMessagesEnabled.

Drafts on UIKit are available since version 4.73.0.

Draft messages are synchronized across devices and work seamlessly offline as well.

Basic Usage

When draft messages are enabled, the logic of saving and deleting drafts will be handled automatically by the SDK. When a user starts typing a message and then navigates away from the conversation, the message content is automatically saved as a draft, for both Channels and Threads.

When the user returns to the conversation, the draft message will be loaded automatically into the composer. If the user clears the composer content or publishes the message, the draft will be deleted.

The draft messages user flow is described in the following diagram:

flowchart LR
    A[User Action]
    A -->|Leave Channel| F[Update Channel Draft]
    A -->|Leave Thread| E[Update Thread Draft]
    A -->|Clear Composer Content| D[Delete Draft]
    A -->|Publish Message| D[Delete Draft]

Customization

By default, the only additional UI added to the SDK when drafts are enabled is a preview of the draft message in the channel list and thread list. When a draft is saved, the draft message is shown in the channel list and thread list until the message is published or deleted.

Channel Draft Preview

The channel draft preview is displayed in the ChatChannelListItemView when thechannel.draftMessage exists. You can customize the default logic by overriding this component.

One simple customization is to change the prefix text. You can do this by overriding the ChatChannelListItemView.draftPrefixText property.

By default, the timestamp of the last message is still displayed, since this is common in most apps. If you want to hide it, you can override the ChatChannelListItemView.updateContent property.

class CustomChannelListItemView: ChatChannelListItemView {
    override func updateContent() {
        super.updateContent()

        // Hide the timestamp
        let hasDraftMessage = content?.channel.draftMessage != nil
        timestampLabel.isHidden = hasDraftMessage
        previewMessageDeliveryStatusView.isHidden = hasDraftMessage
    }
}

Result:

BeforeAfter
Before
After

Thread Draft Preview

The thread draft preview is displayed in the ChatThreadListItemView when the thread.parentMessage.draftReply exists.

Like the channel draft preview, you can also customize the prefix text and hide the timestamp.

As an extra customization, let’s change the reply preview text when there is a draft:

class DemoChatThreadListItemView: ChatThreadListItemView {
    override func updateContent() {
        super.updateContent()

        let hasDraftMessage = content?.thread.parentMessage.draftReply != nil
        replyTimestampLabel.isHidden = hasDraftMessage

        if let draftReply = content?.thread.parentMessage.draftReply {
            replyDescriptionLabel.text = "\(draftReply.text) (Draft)"
            replyDescriptionLabel.textColor = appearance.colorPalette.accentPrimary.withAlphaComponent(0.7)
            replyDescriptionLabel.font = appearance.fonts.footnoteBold
        } else {
            replyDescriptionLabel.text = replyPreviewText
            replyDescriptionLabel.textColor = appearance.colorPalette.subtitleText
            replyDescriptionLabel.font = appearance.fonts.footnote
        }
    }
}

Result:

BeforeAfter
Before
After

Draft List Query

At the moment, the SDK does not provide a default UI component to render the list of drafts from the current user. However, you can easily build your own component by using the ChatClient.shared.currentUserController() to fetch the drafts.

The current user controller has a loadDraftMessages() method that is responsible to query the drafts. It accepts a query: DraftListQuery parameter that allows you to customize the query.

The DraftListQuery has the following properties:

  • pagination: The initial pagination information. By default is Pagination(pageSize: 25, offset: 0)
  • sorting: The sorting criteria for the drafts. By default is [.init(key: .createdAt, isAscending: false)]

Fetching Drafts

Below is an example on how to fetch the drafts with the default query:

// Load the draft messages for the current user
let currentUserController = chatClient.currentUserController()
currentUserController.loadDraftMessages { result in
    switch result {
    case .success(let drafts):
        print("Draft messages loaded: \(drafts)")
    case .failure(let error):
        print("Failed to load draft messages: \(error)")
    }
}

Pagination

In case the currentUserController.hasLoadedAllDrafts is false, you can load the next page of drafts by calling currentUserController.loadMoreDraftMessages(). This method has an optional completion handler that will be called when the page finishes loading.

currentUserController.loadMoreDraftMessages { result in
    switch result {
    case .success(let drafts):
        print("Draft messages loaded: \(drafts)")
    case .failure(let error):
        print("Failed to load more draft messages: \(error)")
    }
}

Delegate

Whenever the drafts are updated or deleted, the CurrentChatUserControllerDelegate will be notified in the following function:

func currentUserController(
    _ controller: CurrentChatUserController,
    didChangeDraftMessages draftMessages: [DraftMessage]
)

To see a full drafts list example implementation, you can check our Demo App example here.

Draft Events

By default, the SDK will react whenever a draft is updated or deleted. But, in case your app needs additional logic, these are the available draft events:

  • DraftUpdatedEvent: Triggered when a draft is updated.
  • DraftDeletedEvent: Triggered when a draft is deleted.

© Getstream.io, Inc. All Rights Reserved.