# Message Read Indicators

For messages sent by the current user, the read indicators represent the read state of the message. It is shown below the message bubble, by default.

## Read indicators states

The possible states of the read indicators are:

- **sent:** The message has been sent to the server successfully. It is shown as a gray checkmark.
- **delivered:** The message has been delivered to at least one of the channel members devices. It is shown as double gray checkmark.
- **read:** The message has been read by at least one of the channel members. It is shown as double blue checkmark.

<admonition type="info">

The **delivered** state is only available since version [4.92.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.92.0) and it needs to be enabled in the Dashboard for each channel type.

</admonition>

| Sent                                                                                     | Delivered                                                                                          | Read                                                                                     | Read by many                                                                                         |
| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| ![Message Delivery State Sent](@chat-sdk/ios/v4/_assets/message-delivery-state-sent.png) | ![Message Delivery State Delivered](@chat-sdk/ios/v4/_assets/message-delivery-state-delivered.jpg) | ![Message Delivery State Read](@chat-sdk/ios/v4/_assets/message-delivery-state-read.png) | ![Message Delivery State Read Group](@chat-sdk/ios/v4/_assets/message-delivery-state-read-group.png) |

<admonition type="info">

If `read_events` are turned OFF for the channel, read indicators are hidden.

</admonition>


## Basic Customization

### Hide read indicators

In case your app does not need to show the read indicators, you can hide them by returning an empty view from the `makeMessageReadIndicatorView` view factory method.

```swift
class CustomAppFactory: ViewFactory {
    @Injected(\.chatClient) public var chatClient

    private init() {}

	public func makeMessageReadIndicatorView(
		channel: ChatChannel,
		message: ChatMessage
	) -> some View {
		return EmptyView()
	}
}
```

| Before                                                                                                 | After                                                                                                |
| ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| ![Hide Message Delivery State Before](@chat-sdk/ios/v4/_assets/hide-message-delivery-state-before.png) | ![Hide Message Delivery State After](@chat-sdk/ios/v4/_assets/hide-message-delivery-state-after.png) |

## Advanced Customization

### Display last read members avatars

The read indicators can be customized to display the last read members avatars by returning a custom view from the `makeMessageReadIndicatorView` view factory method.

Here is an example implementation:

```swift
class CustomAppFactory: ViewFactory {
    @Injected(\.chatClient) public var chatClient

    private init() {}

	public func makeMessageReadIndicatorView(
		channel: ChatChannel,
		message: ChatMessage
	) -> some View {
		return CustomMessageReadIndicatorView(
			channel: channel,
			message: message
		)
	}
}

struct CustomMessageReadIndicatorView: View {
    @Injected(\.chatClient) private var chatClient
    @Injected(\.utils) private var utils

    let channel: ChatChannel
    let message: ChatMessage

    private let avatarSize: CGFloat = 16
    private let avatarOverlap: CGFloat = 8
    private let maxAvatarsToShow = 3

    var body: some View {
        HStack(spacing: 4) {
            defaultMessageReadIndicatorView

            if !lastReadUsers.isEmpty {
                readAvatarsView
            }
        }
    }

    var defaultMessageReadIndicatorView: some View {
        DefaultViewFactory.shared.makeMessageReadIndicatorView(
            channel: channel,
            message: message
        )
    }

	var readAvatarsView: some View {
        HStack(spacing: -avatarOverlap) {
            ForEach(Array(lastReadUsers.enumerated()), id: \.element.id) { index, user in
                MessageAvatarView(
                    avatarURL: user.imageURL,
                    size: CGSize(width: avatarSize, height: avatarSize),
                    showOnlineIndicator: false
                )
                .overlay(
                    Circle()
                        .stroke(Color.white, lineWidth: 1)
                )
                .zIndex(Double(maxAvatarsToShow - index))
            }
        }
    }

	/// Last 3 users who read the message (most recent first)
    private var lastReadUsers: [ChatUser] {
        let reads = channel.reads(for: message)
            .sorted { $0.lastReadAt > $1.lastReadAt }

        return Array(reads.prefix(maxAvatarsToShow)).map(\.user)
    }
}
```

Below you can see a comparison of the default read indicator view and the custom one:

| Before                                                                                                                     | After                                                                                                                    |
| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| ![Customize Message Delivery State Read Before](@chat-sdk/ios/v4/_assets/customize-message-delivery-state-read-before.png) | ![Customize Message Delivery State Read After](@chat-sdk/ios/v4/_assets/customize-message-delivery-state-read-after.png) |

### Show all read and delivered members

To show all the read and delivered members, we can create a custom screen that shows the members that have read the message and the ones that were delivered but not read yet.

For this, we need to add a tap gesture to our custom read indicator view and present this custom view.

```swift
/// Our custom read indicator view from the previous example.
struct CustomMessageReadIndicatorView: View {
    @Injected(\.chatClient) private var chatClient
    @Injected(\.utils) private var utils

    let channel: ChatChannel
    let message: ChatMessage

    private let avatarSize: CGFloat = 16
    private let avatarOverlap: CGFloat = 8
    private let maxAvatarsToShow = 3

	@State private var showMessageReadsInfo = false

    var body: some View {
        HStack(spacing: 4) {
            defaultMessageReadIndicatorView

            if !lastReadUsers.isEmpty {
                readAvatarsView
            }
        }
		.onTapGesture {
            showMessageReadsInfo = true
        }
        .sheet(isPresented: $showMessageReadsInfo) {
            MessageReadsInfoView(
                message: message,
                channelController: chatClient.channelController(for: channel.cid)
            )
        }
    }
}
```

You can check an example implementation of the `MessageReadsInfoView` in our demo app [here](https://github.com/GetStream/stream-chat-swift/blob/develop/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift). It looks something like this:

![Message Delivery State Read and Delivered](@chat-sdk/ios/v4/_assets/message-delivery-state-read-and-delivered.png)

<admonition type="warning">

At the moment, showing the timestamps of the read and delivered members is not recommended since the timestamp is always related to the latest message in the channel, and not for each message individually.

</admonition>


---

This page was last updated at 2026-04-17T17:33:38.315Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/ios/v4/swiftui/message-components/read-indicators/](https://getstream.io/chat/docs/sdk/ios/v4/swiftui/message-components/read-indicators/).