Message Moderation

Stream Chat can moderate messages before they are delivered (bounce) and lets users report messages to moderators (flag). This page shows how to customize the UIKit components for moderated messages. For how moderated messages are represented in the SDK, see the Moderation guide.

Bounced Messages

A bounced message is displayed with an error indicator next to it. When the user long presses the message or taps the indicator, ChatMessageListVC calls showActions(forDebouncedMessage:), which presents an alert with the Send Anyway, Edit Message and Delete Message actions.

To change the actions, or to replace the alert with your own UI, subclass ChatMessageListVC and override messageActions(forDebouncedMessage:) or showActions(forDebouncedMessage:):

class CustomChatMessageListVC: ChatMessageListVC {
    // Change the list of actions, e.g. do not allow sending a bounced message anyway.
    override func messageActions(forDebouncedMessage message: ChatMessage) -> [ChatMessageActionItem] {
        super.messageActions(forDebouncedMessage: message)
            .filter { !($0 is ResendActionItem) }
    }

    // Or replace the default alert completely.
    override func showActions(forDebouncedMessage message: ChatMessage) {
        let actions = messageActions(forDebouncedMessage: message)
        let alert = UIAlertController(
            title: "Message not sent",
            message: "This message goes against our community guidelines.",
            preferredStyle: .alert
        )
        actions.forEach { item in
            alert.addAction(UIAlertAction(
                title: item.title,
                style: item.isDestructive ? .destructive : .default,
                handler: { _ in item.action(item) }
            ))
        }
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        present(alert, animated: true)
    }
}

Components.default.messageListVC = CustomChatMessageListVC.self

The error indicator is a ChatMessageErrorIndicator. You can change its icon with Appearance.default.images.messageListErrorIndicator, or provide your own subclass:

class CustomErrorIndicator: ChatMessageErrorIndicator {
    override func setUpAppearance() {
        super.setUpAppearance()
        setImage(UIImage(systemName: "exclamationmark.shield.fill"), for: .normal)
        tintColor = .systemOrange
    }
}

Components.default.messageErrorIndicator = CustomErrorIndicator.self
Note:

The error indicator is also used for messages that failed to be sent for other reasons, for example because of a network error.

Flag Action

The flag action is created by ChatMessageActionsVC.flagActionItem(). It shows a confirmation alert and then calls flag() on the message controller. To change the title, the behaviour, or to flag the message with a reason, subclass ChatMessageActionsVC and override flagActionItem():

class CustomChatMessageActionsVC: ChatMessageActionsVC {
    override func flagActionItem() -> ChatMessageActionItem {
        FlagActionItem(
            title: "Report",
            action: { [weak self] _ in
                guard let self else { return }
                self.alertsRouter.showMessageFlagConfirmationAlert { confirmed in
                    guard confirmed else { return }
                    self.messageController.flag(reason: "inappropriate") { _ in
                        self.delegate?.chatMessageActionsVCDidFinish(self)
                    }
                }
            },
            appearance: appearance
        )
    }
}

Components.default.messageActionsVC = CustomChatMessageActionsVC.self

The icon of the default flag action can be changed with Appearance.default.images.messageActionFlag.

To hide the flag action, override messageActions and filter it out:

class CustomChatMessageActionsVC: ChatMessageActionsVC {
    override var messageActions: [ChatMessageActionItem] {
        super.messageActions.filter { !($0 is FlagActionItem) }
    }
}

To customize the confirmation alert, subclass AlertsRouter and override showMessageFlagConfirmationAlert(confirmed:):

class CustomAlertsRouter: AlertsRouter {
    override func showMessageFlagConfirmationAlert(confirmed: @escaping (Bool) -> Void) {
        let alert = UIAlertController(
            title: "Report message?",
            message: "Our moderators will review this message.",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in confirmed(false) })
        alert.addAction(UIAlertAction(title: "Report", style: .destructive) { _ in confirmed(true) })
        rootViewController.present(alert, animated: true)
    }
}

Components.default.alertsRouter = CustomAlertsRouter.self

For more details about replacing components, see Custom Components and Customize Message Actions.

Changing the Texts

If you only need to change the texts, override the following keys in your app's Localizable.strings:

Key Default value
message.moderation.title Are you sure?
message.moderation.message Consider how your comment might...
message.moderation.resend Send Anyway
message.moderation.edit Edit Message
message.moderation.delete Delete Message
message.actions.flag Flag Message
message.actions.flag.confirmation-title Flag Message
message.actions.flag.confirmation-message Do you want to send a copy of...

See the localization guide for more details.

Add Chat to my app: getstream.io/SKILL.md

The fastest way to build with Stream. Start a new project or improve an existing one. Full CLI and documentation integration out of the box.


Ask your agent:

/stream Build me a Social App with Feeds and Moderation.
/stream Any livestream calls running?
/stream Chat iOS v5: <Your Question>