# Channel List

The `ChatChannelListVC` is the UI component used to display a list of channels matching the given query.

<admonition type="note">

The Channel List screen is backed by the low-level `ChatChannelListController` component which fetches channels from the API and keeps the list in sync with the remote.
Read more about channel list query and how `ChatChannelListController` works [here](/chat/docs/sdk/ios/v4/client/controllers/channels/).

</admonition>

## Basic Usage

The first step to show the channel list screen in your app is to create `ChatChannelListVC` instance:

```swift
// Create a channel list where the current user is a member.
guard let currentUserId = client.currentUserId else { return }
let query = ChannelListQuery(filter: .containMembers(userIds: [currentUserId]))
let controller = ChatClient.shared.channelListController(query: query)
let channelListVC = ChatChannelListVC.make(with: controller)
```

When the `ChatChannelListVC` instance is created, there are multiple ways of showing it:

1. modally
1. inside an existing `UINavigationController`
1. as a tab inside `UITabBarController`
1. inside `UISplitViewController`
1. as a child controller

To present the channel list modally:

```swift
let navigationVC = UINavigationController(rootViewController: channelListVC)
present(navigationVC, animated: true)
```

To push the channel list to an existing navigation controller:

```swift
navigationController?.pushViewController(channelListVC, animated: true)
```

To show the channel list as a tab:

```swift
let navigationVC = UINavigationController(rootViewController: channelListVC)
let tabBatVC = UITabBarController()
tabBatVC.viewControllers = [..., navigationVC]
```

To show the channel list as a main screen in split view controller and the selected channel as a detail:

```swift
let channelListNVC = UINavigationController(rootViewController: channelListVC)
let splitVC = UISplitViewController()
splitVC.preferredDisplayMode = .oneBesideSecondary
splitVC.viewControllers = [
    channelListNVC,
    // Optionally provide a controller shown as a detail till user opens a channel.
]
```

To show the channel list as a child:

```swift
class ParentVC: UIViewController {
    let containerView: UIView

    override func viewDidLoad() {
        super.viewDidLoad()

        let navigationVC = UINavigationController(rootViewController: channelListVC)
        addChild(navigationVC)
        navigationVC.view.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(navigationVC.view)
        NSLayoutConstraint.activate([
            navigationVC.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            navigationVC.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            navigationVC.view.topAnchor.constraint(equalTo: containerView.topAnchor),
            navigationVC.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
        ])
        navigationVC.didMove(toParent: self)
    }
}
```

## Navigation

The Channel List uses the [`ChatChannelListRouter`](https://github.com/GetStream/stream-chat-swift/blob/develop/Sources/StreamChatUI/Navigation/ChatChannelListRouter.swift) to navigate to each channel and handle the swipe gesture actions. You can customize the navigation behavior by overriding the `Components.default.channelListRouter`:

```swift
Components.default.channelListRouter = CustomChannelListRouter.self
```

By default, some of the actions are not handled by the SDK, like the delete channel action, showing the current user profile or showing the channel actions. Here is an example on how to handle those:

```swift
class CustomChannelListRouter: ChatChannelListRouter {
    override func showCurrentUserProfile() {
        guard let currentUserId = ChatClient.shared.currentUserId else { return }
        rootViewController.show(userProfileViewController, sender: self)
    }

    override func didTapDeleteButton(for cid: ChannelId) {
        let controller = ChatClient.shared.channelController(for: cid)
        controller.deleteChannel { [weak self] error in
            // handle error case, if the request is successful, the channel
            // will be deleted and removed from the list automatically.
        }
    }

    override func didTapMoreButton(for cid: ChannelId) {
        let controller = ChatClient.shared.channelController(for: cid)
        // Show custom actions view for the channel.
    }
}
```

## Channel List Query

The `ChannelListQuery` is responsible to configure the list of channels that will be displayed in the `ChatChannelListController`. These are the available parameters:

- `filter`: Responsible to filter the list of channels that will be displayed.
- `sort`: Responsible to sort the list of channels. By default, the channel list will be sorted by their last message date (or channel creation date, if the channel is empty).
- `pageSize`: Responsible to specify how many channels the initial page will show. The default is 20.
- `messagesLimit`: Responsible to specify how many messages each channel will include. The default is 25. To improve performance, you can reduce this number to something like 5.
- `membersLimit`: Responsible to specify how many members each channel will include. The default is 30. To improve performance, you can reduce this number to something like 10.

Below you can find some examples of how to create a `ChannelListQuery` with different parameters.

```swift
// Filter for channels where the current user is a member.
let query = ChannelListQuery(filter: .containMembers(userIds: [currentUserId]))

// Filter channels that have at least one message.
let query = ChannelListQuery(filter: .nonEmpty)

// Compound Filter for unread channels the current user has.
let query = ChannelListQuery(filter: .and([
    .hasUnread,
    .containMembers(userIds: [currentUserId])
]))

// Sorting channels by unread count and last message date.
let query = ChannelListQuery(
    filter: .containMembers(userIds: [currentUserId]),
    sort: [
        .init(key: .lastMessageAt, isAscending: false),
        .init(key: .hasUnread, isAscending: false),
    ],
    pageSize: 10,
    messagesLimit: 5,
    membersLimit: 5
)
```

## UI Customization

You can customize the Channel List UI by overriding the `ChatChannelListVC` component and the `ChatChannelListItemView`. For example, in `ChatChannelListVC` you can modify the header and state views, while in `ChatChannelListItemView` you can adjust how each channel is displayed.

### Channel List Header

The header of the channel can be configured the same way you would configure a native `UIViewController` object. Here is an example on how to customize the header:

```swift
class CustomChannelListVC: ChatChannelListVC {

    lazy var createChannelButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(systemName: "plus.message")!, for: .normal)
        return button
    }()

    override open func setUpAppearance() {
        super.setUpAppearance()

        title = "Channels"

        navigationItem.rightBarButtonItems = [
            UIBarButtonItem(customView: createChannelButton)
        ]
        createChannelButton.addTarget(
            self,
            action: #selector(didTapCreateNewChannel),
            for: .touchUpInside
        )
    }
}

// Use your custom list view controller when presenting the channel list.
// Reuse the channelListController you created above.
let channelListVC = CustomChannelListVC()
channelListVC.controller = channelListController
```

### Channel List States

You can opt to show an empty, error and loading view by setting the following flag to true in the `Components` configuration:

```swift
Components.default.isChatChannelListStatesEnabled = true
```

This feature is disabled by default, having just the standard loading indicator for the loading state. By enabling this feature, the StreamChat SDK will handle the channel list view states automatically for you.

| Empty                                                               | Error                                                               | Loading                                                                 |
| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| ![Empty View](@chat-sdk/ios/v4/_assets/channel-list-empty-view.png) | ![Error View](@chat-sdk/ios/v4/_assets/channel-list-error-view.png) | ![Loading View](@chat-sdk/ios/v4/_assets/channel-list-loading-view.png) |

You can customize the empty, error or loading view by subclassing `ChatChannelListEmptyView`, `ChatChannelListErrorView` or `ChatChannelListLoadingView` respectively.

As an example, let's see how you can customize the error view:

```swift
class CustomChatChannelListVC: ChatChannelListVC {

    override open func setUpAppearance() {
        super.setUpAppearance()

        channelListErrorView.backgroundColor = .red
        channelListErrorView.titleLabel.text = "Data unavailable"
        channelListErrorView.titleLabel.textColor = .black
        channelListErrorView.retryButton.setImage(.init(systemName: "hourglass.circle"), for: .normal)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        channelListErrorView.layer.cornerRadius = 20
    }
}

Components.default.channelListErrorView = CustomChannelListErrorView.self
```

![Error View](@chat-sdk/ios/v4/_assets/channel-list-custom-error-view.png)

### Channel List Item

The item view used in the Channel List is represented by the `ChatChannelListItemView` component. It is responsible for displaying each channel in the list, including the channel name, avatar, last message and the unread indicator.

For example, if you want to create a custom unread indicator (like a blue dot similar to iMessage), you can subclass `ChatChannelListItemView` and override the layout:

```swift
class iMessageChannelListItemView: ChatChannelListItemView {

    // A simple blue dot as the unread indicator
    private lazy var customUnreadView: UIView = {
        let unreadView = UIView()
        unreadView.backgroundColor = tintColor
        unreadView.layer.masksToBounds = true
        unreadView.layer.cornerRadius = 5
        unreadView.clipsToBounds = true
        return unreadView
    }()

    override func setUpLayout() {
        super.setUpLayout()

        // Size the unread dot
        NSLayoutConstraint.activate([
            customUnreadView.widthAnchor.constraint(equalTo: customUnreadView.heightAnchor),
            customUnreadView.widthAnchor.constraint(equalToConstant: 10),
        ])

        // Show at the start of the row
        mainContainer.insertArrangedSubview(customUnreadView, at: 0)

        // Remove the default unread count badge
        topContainer.removeArrangedSubview(unreadCountView)
    }

    override func updateContent() {
        super.updateContent()

        // Only show the blue dot if unread
        customUnreadView.alpha = unreadCountView.content == .noUnread ? 0 : 1
    }
}

Components.default.channelContentView = iMessageChannelListItemView.self
```

## Search

The UI SDK provides searching components out-of-the-box for the Channel List. Currently, there is two types of searching strategies that can be enabled, searching by messages or channels.

To enable it, a `ChannelListSearchStrategy` should be configured in the `Components` configuration:

```swift
// If you want to enable the search by messages
Components.default.channelListSearchStrategy = .messages
// If you want to enable the search by channels
Components.default.channelListSearchStrategy = .channels
```

In order to customize the search UI component, you can provide a subclass of our default components, `ChatMessageSearchVC` and `ChatChannelSearchVC`, like so:

```swift
// If you want to enable the search by messages with a custom component.
Components.default.channelListSearchStrategy = .messages(CustomMessageSearchVC.self)
// If you want to enable the search by channels with a custom component.
Components.default.channelListSearchStrategy = .channels(CustomChatChannelSearchVC.self)
```

Both `ChatMessageSearchVC` and `ChatChannelSearchVC` inherit from the Channel List component, so all the customization provided in the Channel List should be reflected in the search components. They also use the same cell component found in the Channel List, so in case you want to customize the search cell component, you can do it by customizing the `ChannelListItemView`. The Channel List Item View contains a `searchResult` in the `content` property in case the view is being used for searching.

In case your app requires a total custom search UI component you can build your own from scratch. To do so, you need to customize the `ChatChannelListVC.setUp()` lifecycle method and provide a `UISearchController` by setting the `navigationItem.searchController`.

## Replacing Channel List Query

In case you want to apply filters to your Channel List you can do this by replacing the channel list query of the `ChatChannelListVC` with either `replaceQuery()` function, or `replaceChannelListController()`. As an example, let's see how you can change the Channel List data to show hidden channels only:

```swift
class CustomChatChannelListVC: ChatChannelListVC {

    lazy var hiddenChannelsButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(systemName: "archive")!, for: .normal)
        return button
    }()

    override open func setUp() {
        super.setUp()

        hiddenChannelsButton.addTarget(self, action: #selector(didTapHiddenChannelsButton), for: .touchUpInside)
    }

    @objc private func didTapHiddenChannelsButton(_ sender: Any) {
        guard let currentUserId = controller.client.currentUserId else { return }
        let hiddenChannelsQuery: ChannelListQuery = .init(filter: .and([
            .containMembers(userIds: [currentUserId]),
            .equal(.hidden, to: true)
        ]))
        self.replaceQuery(hiddenChannelsQuery)
    }
}
```

You can see a full working example in our Demo App [here](https://github.com/GetStream/stream-chat-swift/blob/add/replace-query-in-channel-list/DemoApp/StreamChat/Components/DemoChatChannelListVC.swift).

<admonition type="note">

The replacing of the Channel List Query is only available after **4.32.0** version.

</admonition>

## Marking all channels as Read

When you're displaying, or loading a set of channels, you may want to mark all the channels as read. For this, the `ChannelListController` has `markAllRead` function:

```swift
controller.markAllRead()
```

This function will reset the unread count for all the channels the controller paginates.


---

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

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/ios/v4/uikit/components/channel-list/](https://getstream.io/chat/docs/sdk/ios/v4/uikit/components/channel-list/).