> ## Documentation Index
> Fetch the complete documentation index at: https://cometchat-22654f5b-docs-ios-pin-save-threads-parity.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Threaded Messages

> Add threaded messages in CometChat iOS UI Kit with parent message context, reply views, message composers, and thread navigation.

Enhance your iOS chat app with threaded messaging by integrating CometChat's UIKit for iOS, allowing users to reply to specific messages within a focused thread view.

## Overview

Threaded messages allow users to reply to specific messages within a conversation, creating a sub-conversation for improved clarity and context. With CometChat's UIKit for iOS, you can:

* Display a dedicated thread view
* View and send replies to a selected message
* Maintain context between the main conversation and the thread

## Prerequisites

Before implementing this feature, ensure you have:

1. Completed [Getting Started](/ui-kit/ios/getting-started) setup
2. CometChat UIKit v5+ installed
3. User logged in with `CometChatUIKit.login()`

## Components

| Component                        | Description                                                   |
| -------------------------------- | ------------------------------------------------------------- |
| `CometChatMessageList`           | Displays messages and provides `onThreadRepliesClick` handler |
| `CometChatThreadedMessageHeader` | Shows the parent message context at the top of the thread     |
| `CometChatMessageComposer`       | Composes messages with an optional `parentMessageId`          |
| `ThreadedMessagesVC`             | View controller that hosts the threaded conversation          |

## Integration Steps

### Step 1: Show the "Reply in Thread" Option

Navigate to the thread when a message's thread icon is tapped:

```swift lines theme={null}
import UIKit
import CometChatUIKitSwift
import CometChatSDK

class MessagesViewController: UIViewController {
    
    var messageListView: CometChatMessageList!
    
    func setupThreadReplies() {
        // Handle thread replies click
        messageListView.set(onThreadRepliesClick: { [weak self] message, _ in
            guard let self = self else { return }
            
            // Create and configure thread view controller
            let threadVC = ThreadedMessagesVC()
            threadVC.parentMessage = message
            
            // Navigate to thread screen
            self.navigationController?.pushViewController(threadVC, animated: true)
        })
    }
}
```

This captures user intent and opens a focused thread screen.

**File reference:** [`ThreadedMessagesVC.swift`](https://github.com/cometchat/cometchat-uikit-ios/blob/v5/SampleApp/View%20Controllers/CometChat%20Components/ThreadedMessagesVC.swift)

### Step 2: Navigate to the Thread Screen

Show a dedicated UI for thread replies. In `ThreadedMessagesVC.swift`:

```swift lines theme={null}
import UIKit
import CometChatUIKitSwift
import CometChatSDK

class ThreadedMessagesVC: UIViewController {
    
    var parentMessage: BaseMessage?
    var user: User?
    var group: Group?
    
    private var parentMessageContainerView: CometChatThreadedMessageHeader!
    private var messageListView: CometChatMessageList!
    private var composerView: CometChatMessageComposer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    private func setupUI() {
        // Add UI components to view hierarchy
        view.addSubview(parentMessageView)
        view.addSubview(messageListView)
        view.addSubview(composerView)
    }
}
```

Header configuration:

```swift lines theme={null}
// Create the threaded message header
let parentMessageContainerView = CometChatThreadedMessageHeader()
```

Message list configuration:

```swift lines theme={null}
// Configure message list with user and parent message
messageListView.set(user: user, parentMessage: parentMessage)
```

This provides a focused UI for thread interactions.

### Step 3: Send a Threaded Message

Ensure new replies are attached to the correct parent message:

```swift lines theme={null}
// Set the parent message ID for threaded replies
composerView.set(parentMessageId: parentMessage?.id ?? 0)
```

Set the conversation context:

```swift lines theme={null}
// Configure composer for user conversation
composerView.set(user: user)

// Or configure for group conversation
composerView.set(group: group)
```

### Step 4: Fetch and Display Thread Replies

Only messages that are part of the thread are displayed. This is handled internally by:

```swift lines theme={null}
// Configure message list to fetch thread replies
messageListView.set(user: user, parentMessage: parentMessage)
```

This ensures `CometChatMessageList` fetches replies using the `parentMessageId`.

## Customization Options

### Header Styling

Customize `CometChatThreadedMessageHeader` appearance:

```swift lines theme={null}
// Customize fonts, colors, and layout
let headerStyle = ThreadedMessageHeaderStyle()
headerStyle.titleFont = UIFont.systemFont(ofSize: 16, weight: .semibold)
headerStyle.titleColor = .label
parentMessageContainerView.set(style: headerStyle)
```

### Composer

Modify placeholder text, input styles, and icons:

```swift lines theme={null}
// Customize composer appearance
composerView.set(placeholderText: "Reply in thread...")
```

### Navigation

Add a custom back button for navigation:

```swift lines theme={null}
// Add custom back button
navigationItem.leftBarButtonItem = UIBarButtonItem(
    image: UIImage(systemName: "chevron.left"),
    style: .plain,
    target: self,
    action: #selector(goBack)
)
```

## Edge Cases

| Scenario               | Handling                                         |
| ---------------------- | ------------------------------------------------ |
| Parent message deleted | Display a fallback UI or disable the composer    |
| No replies             | Show an empty state (e.g., "No replies yet")     |
| Offline mode           | Disable the composer and queue thread operations |

## Error Handling

| Error Type     | Solution                                                              |
| -------------- | --------------------------------------------------------------------- |
| Fetch failures | Show an error UI or retry mechanism when loading thread messages      |
| Send failures  | Handle send errors via delegate callbacks or show an alert with retry |
| Loading states | Display a `UIActivityIndicatorView` during fetch/send operations      |
| Blocked users  | Remove the composer and display a blocked status label                |

## Thread Subscription

<Note>
  **Available from UI Kit v5.1.22.** This feature requires `CometChatUIKitSwift` v5.1.22 or later, which depends on `CometChatSDK` v4.1.9.
</Note>

Thread subscription gives users Slack-style control over thread noise: they can **subscribe** to a thread to be notified about its replies, or **unsubscribe** from one to mute it. Users are automatically subscribed when they start a thread, reply in one, or are @-mentioned in one — subscribing explicitly is how they opt in to a conversation they haven't participated in yet.

The UI Kit ships the toggle as a **Subscribe to thread / Unsubscribe from thread** option in the message action sheet, and broadcasts every change on the event bus so any control you host elsewhere — such as a button in your thread screen's title bar — stays in sync.

### The Surfaces

Thread subscription surfaces in two places: the message action sheet on [CometChatMessageList](/ui-kit/ios/message-list), and a bell in the thread screen's navigation bar, rendered by [CometChatMessageHeader](/ui-kit/ios/message-header).

Both are shown by default — unlike pin and save, the feature is not gated on a dashboard setting. Each surface is hidden independently, per instance, with `hideThreadSubscriptionOption` and `hideThreadSubscriptionButton` respectively.

### Surface 1: The Message Action Sheet Option

`CometChatMessageList` adds a **Subscribe to thread** / **Unsubscribe from thread** option to the long-press action sheet. The label and icon reflect the current state, read synchronously from the SDK when the sheet is built.

The option appears only on **parent messages** — never on a reply inside a thread:

```swift theme={null}
// The kit's gate, for reference:
// !hideThreadSubscriptionOption && parentMessageId == 0
```

<Note>
  A subscription is always rooted at the thread's parent message. Offering the option on a reply would create a thread row the user can never open, so the kit hides it there entirely. It is **not** gated on reply count — subscribing to a message with no replies yet is the point.
</Note>

<Info>
  Thread subscription is offered in **one-on-one conversations as well as groups** — there is no receiver-type check. Subscribing applies to a 1-1 thread, and unsubscribing genuinely suppresses its notifications.
</Info>

To hide the option while keeping the rest of the feature:

```swift theme={null}
messageListView.hideThreadSubscriptionOption = true
```

To replace the kit's behavior with your own, supply an `onItemClick` on a custom option with the id `MessageOptionConstants.threadSubscription` — the kit calls your handler instead of its own.

### Surface 2: The Bell in the Navigation Bar

[CometChatMessageHeader](/ui-kit/ios/message-header) renders a subscribe/unsubscribe bell in its trailing area. Set `parentMessage` to put the header in **thread mode** — the bell renders only then, so a conversation header is unaffected. As with the action-sheet option, it renders in one-on-one threads as well as groups.

This is the bell to use. Every CometChat platform places the subscription bell in the thread screen's **top bar**, not in the reply-count row beneath it — on iOS, putting `CometChatMessageHeader` in thread mode is how you get it there.

```swift theme={null}
let messageHeaderView = CometChatMessageHeader()
messageHeaderView.set(parentMessage: parentMessage)     // puts the header in thread mode
```

Host it as the thread screen's navigation bar — pin it to the safe-area top and hide the system bar, exactly as a conversation screen does:

```swift lines theme={null}
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    navigationController?.setNavigationBarHidden(true, animated: animated)
}

func buildUI() {
    view.addSubview(messageHeaderView)
    view.addSubview(threadHeaderView)   // reply-count bar, directly beneath
    view.addSubview(messageListView)

    NSLayoutConstraint.activate([
        messageHeaderView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        messageHeaderView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        messageHeaderView.trailingAnchor.constraint(equalTo: view.trailingAnchor),

        threadHeaderView.topAnchor.constraint(equalTo: messageHeaderView.bottomAnchor),
        threadHeaderView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        threadHeaderView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    ])
}
```

<Note>
  [CometChatThreadedMessageHeader](/ui-kit/ios/threaded-messages-header) carries a **fallback** bell on its reply-count bar, so a thread screen with no top bar still has the control. A screen that renders both components should hide it, leaving the top-bar bell as the only one:

  ```swift theme={null}
  threadHeaderView.hideThreadSubscriptionButton = true
  ```
</Note>

The bell tracks state on its own: it reads the current subscription state from the SDK, flips optimistically on tap, reverts if the request fails, toasts in both directions, and emits `ccThreadSubscriptionChanged` on success. You do not wire any of that up.

If your screen draws a subscription control of its own in the navigation bar, suppress the kit's:

```swift theme={null}
messageHeaderView.set(hideThreadSubscriptionButton: true)
```

<Note>
  The bell needs `parentMessage`, on a parent whose message has been sent. Without it a conversation header renders nothing — that is deliberate, so a thread control can never appear on a non-thread screen.
</Note>

### Cross-Surface Sync

Both surfaces observe the UI Kit event bus, so toggling in one place updates the other without a refetch. After a successful toggle the kit emits:

```swift theme={null}
CometChatThreadEvents.ccThreadSubscriptionChanged(parentMessageId: parentMessageId,
                                                  isSubscribed: !isSubscribed)
```

Both header components observe this themselves, so the bell stays correct when the user toggles from the action sheet — no wiring needed. Conform to `CometChatThreadEventListener` only to keep a control of **your own** in step:

```swift theme={null}
extension ThreadedMessagesVC: CometChatThreadEventListener {
    func ccThreadSubscriptionChanged(parentMessageId: Int, isSubscribed: Bool) {
        guard parentMessageId == parentMessage?.id else { return }
        renderThreadSubscription(isSubscribed: isSubscribed)
    }
}
```

<Warning>
  Thread listeners are keyed by id, and registering a duplicate id **evicts** the previous listener. Use a distinct id per screen — the kit randomises its own for exactly this reason.
</Warning>

See [Events](/ui-kit/ios/events) for the full event reference.

### Behavior

* **Optimistic with revert** — the control flips instantly on tap, keeps one request in flight per thread, and reverts if the server rejects the change. An offline tap fails visibly and reverts; nothing is queued.
* **No event on failure** — the kit emits `ccThreadSubscriptionChanged` only on success, so every surface keeps showing the state the server still holds.
* **Toasts in both directions** — subscribing and unsubscribing each confirm with a toast, and both toggle sites use the same copy.
* **Unsubscribing is not sticky** — replying again, or being @-mentioned, re-subscribes the user. The kit says so in the toast rather than letting the user discover it.
* **Unknown state renders as unsubscribed** — a message whose subscription state hasn't been learned yet (for example, one that just arrived in real time) shows the enabled subscribe control, never a spinner.
* **VoiceOver announces the subscribed state** — the subscribed control carries an accessibility label that is not shown as visible text.

### Notifications

Whether a subscribed thread actually produces a push notification is governed by the user's notification preferences: the replies preference supports notifying only for **threads the user is subscribed to** (`SUBSCRIBE_TO_SUBSCRIBED_THREADS`). See [Thread Subscription (SDK)](/sdk/ios/thread-subscription#notification-preferences).

## Feature Matrix

| Feature                   | Implementation                                   |
| ------------------------- | ------------------------------------------------ |
| Show thread option        | `CometChatMessageList.onThreadRepliesClick`      |
| Thread view screen        | `ThreadedMessagesVC.swift`                       |
| Display threaded messages | `CometChatMessageList.set(parentMessage:)`       |
| Send threaded message     | `CometChatMessageComposer.set(parentMessageId:)` |
| Thread header             | `CometChatThreadedMessageHeader`                 |
| Handle blocked user       | Remove composer and show a blocked user label    |

## Related Components

* [Message List](/ui-kit/ios/message-list) - Display messages in conversations
* [Message Composer](/ui-kit/ios/message-composer) - Compose and send messages
* [Threaded Messages Header](/ui-kit/ios/threaded-messages-header) - Thread header component

<CardGroup cols={2}>
  <Card title="Sample App" href="https://github.com/cometchat/cometchat-uikit-ios/tree/v5/SampleApp">
    Explore a complete sample application demonstrating threaded messaging
  </Card>

  <Card title="UIKit Source" href="https://github.com/cometchat/cometchat-uikit-ios/tree/v5">
    Browse the CometChat UIKit for iOS source code
  </Card>
</CardGroup>

## Related Guides

<CardGroup cols={2}>
  <Card title="Thread Subscription (SDK)" icon="bell" href="/sdk/ios/thread-subscription">
    The underlying subscription APIs, including building a thread inbox
  </Card>

  <Card title="Threaded Messages Header" icon="heading" href="/ui-kit/ios/threaded-messages-header">
    Customize the thread header component
  </Card>

  <Card title="Message List" icon="list" href="/ui-kit/ios/message-list">
    Display and customize chat messages
  </Card>

  <Card title="Core Features" icon="star" href="/ui-kit/ios/core-features">
    Overview of messaging features
  </Card>
</CardGroup>
