---
source_url: https://www.pubnub.com/docs/chat/community-supported/ios/ui-components
title: UI components for PubNub Chat Components for iOS
updated_at: 2026-06-19T11:34:46.979Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# UI components for PubNub Chat Components for iOS

PubNub Chat Components for iOS provide easy-to-use building blocks to create chat applications for various use cases with different functionalities and customizable looks.

Available components include:

* [ChannelList](#channellist)
* [MemberList](#memberlist)
* [MessageList](#messagelist) (with [Message Reactions UI](#message-reactions))
* [MessageInput](#messageinput)

## ChannelList

`UIViewController` that displays a list of stored `Channel` objects. It can represent all channels of the application, only channels joined by the current user, all channels available to be joined, or any other type of [NSFetchedResultsController](https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller) made against stored channel data.

### Parameters

You can initialize the `ChannelList` view model using the following parameters.

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `provider` | `ChatProvider` | n/a | The `ChatProvider` instance to be used inside this component. |
| `fetchedEntities` | `NSFetchedResultsController` | n/a | The data type and query options used to populate the component. |
| `componentTheme` | `ChannelListComponentTheme?` | The `ChannelList` theme sourced from `ChatProvider`. | The configurable theme used for the component. If you provide a custom object, you must maintain it outside of `ChatProvider`. |

### View model

The view model is used to configure both the main `UIViewController` for the component, the channel data, and lay out any sub-components.

To override core functionality, you can either subclass the view model or implement override blocks to replace specific key functionalities.

The default `ChannelListComponentViewModel` is configured to display the default channel memberships of the current user, sorted by the type and name of the channel.

#### Component population

`ChatProvider` can be extended to return a configured `NSFetchedResultsController`, and this object ensures the component always displays the most recent data. `NSFetchedResultsController` can contain any type of stored data related to a channel, and can be sorted and sectioned based on any channel property.

#### Default example

By default, the list is populated by any channel of which the current user is a member. The list is divided into multiple sections based on the channel type, and each section is sorted by the name of the channel.

```swift
open func channelMembershipsFrom(
  userId: String, sectionedByType: Bool
) -> NSFetchedResultsController<ManagedEntities.Member> {

  let request = ManagedEntities.Member.membershipsBy(userId: userId)

  request.sortDescriptors = [
    NSSortDescriptor(key: "channel.type", ascending: false),
    NSSortDescriptor(key: "channel.name", ascending: true)
  ]
  request.relationshipKeyPathsForPrefetching = ["channel"]

  return fetchedResultsControllerProvider(
    fetchRequest: request,
    sectionKeyPath: sectionedByType ? "channel.type": nil,
    cacheName: nil
  )
}
```

You can create a view model composed of the above default `NSFetchedResultsController` and `ChannelListComponentTheme` using this `ChatProvider` extension method:

```swift
open func senderMembershipsChannelListComponentViewModel(
  sectionedByType: Bool = true,
  customTheme: ChannelListComponentTheme? = nil
) -> ChannelListComponentViewModel<ModelData, ManagedEntities> {
  return ChannelListComponentViewModel(
    provider: self,
    fetchedEntities: channelMembershipsFrom(userId: senderID, sectionedByType: sectionedByType),
    customTheme: customTheme ?? self.themeProvider.template.channelListComponent
  )
}
```

#### Properties

The `ChannelList` view model provides outlets for the following properties after initialization:

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `provider` | `ChatProvider` | n/a | A `ChatProvider` instance to be used inside this component. |
| `fetchedEntities` | `NSFetchedResultsController` | n/a | The data type and query options used to populate the component. |
| `componentTheme` | `ChannelListComponentTheme` | The `ChannelList` theme sourced from `ChatProvider`. | The configurable theme used for the component. |
| `leftBarButtonNavigationItems` | `(UIViewController, ChannelListComponentViewModel) -> [UIBarButtonItem]` | `nil` | Views that appear on the left side of the navigation bar. |
| `rightBarButtonNavigationItems` | `(UIViewController, ChannelListComponentViewModel) -> [UIBarButtonItem]` | `nil` | Views that appear on the right side of the navigation bar. |
| `customNavigationTitleView` | `(ChannelListComponentViewModel) -> UIView?` | `nil` | The custom view that's used inside the navigation bar title. |
| `customNavigationTitleString` | `(ChannelListComponentViewModel) -> AnyPublisher<String?, Never>` | `nil` | The custom `String` publisher that's used inside the navigation bar title if a view isn't provided. |
| `customChannelCellViewModel` | `(ChannelListComponentViewModel, Channel) -> ChannelCellComponentViewModel` | `nil` | The cell view model that's be used instead of the default model found inside the theme. |
| `configureFetchedResultsController` | `(NSFetchedResultsController) -> Void` | `nil` | An outlet to customize the channel data query before the data is fetched. |

You can configure the message items inside the list with the data found inside `ManagedChannelViewModel`:

```swift
public protocol ManagedChannelViewModel {
  associatedtype Entity: ManagedChatChannel
  associatedtype MemberViewModel: ManagedMemberViewModel & Hashable
  associatedtype MessageViewModel: ManagedMessageViewModel & Hashable

  var pubnubId: String { get }
  var managedObjectId: NSManagedObjectID { get }

  var channelNamePublisher: AnyPublisher<String, Never> { get }
  var channelDetailsPublisher: AnyPublisher<String?, Never> { get }
  var channelAvatarUrlPublisher: AnyPublisher<URL?, Never> { get }
  var channelTypePublisher: AnyPublisher<String, Never> { get }
  var channelCustomPublisher: AnyPublisher<Data, Never> { get }

  var membershipPublisher: AnyPublisher<Set<MemberViewModel>, Never> { get }
  var memberCountPublisher: AnyPublisher<Int, Never> { get }
  var presentMemberCountPublisher: AnyPublisher<Int, Never> { get }

  var messagesPublisher: AnyPublisher<Set<MessageViewModel>, Never> { get }
  var oldestMessagePublisher: AnyPublisher<MessageViewModel?, Never> { get }
}
```

### Actions

This component supports the following UI interactions:

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `componentDidLoad` | `(UIViewController, ChannelListComponentViewModel) -> Void` | `nil` | An action called when the component view is loaded. |
| `componentWillAppear` | `(UIViewController, ChannelListComponentViewModel) -> Void` | `nil` | An action called when the component view appears on the screen. |
| `componentWillDisappear` | `(UIViewController, ChannelListComponentViewModel) -> Void` | `nil` | An action called when the component view disappears from the screen. |
| `didSelectChannel` | `(UIViewController, ChannelListComponentViewModel, ChatChannel) -> Void` | Default `MessageList` component | An action called when a user taps a list item. |

#### Example

The following example shows how an action can be configured after creating a view model. `didSelectChannel` creates a default `MessageList` component from the selected channel and displays it from the current `ChannelList` component.

```swift
let channelViewModel = chatProvider
  .senderMembershipsChannelListComponentViewModel()

channelViewModel.didSelectChannel = { (controller, viewModel, channel) in

  // Prepare Message List View
  guard let component = try? viewModel.provider
          .messageListComponentViewModel(for: channel.id)
          .configuredComponentView() else { return }

  // Display it from the current controller in the primary content view
  controller.show(component, sender: nil)
}
```

## MemberList

`UIViewController` that displays a list of stored `Member` and `User` objects. It can represent all members of a specific channel, only other users that are currently present in the application, a sectioned list of present and non-present members, or any other type of [NSFetchedResultsController](https://developer.apple.com/documentation/coredata/nsfetchedresultscontroller) made against stored member data.

### Parameters

You can initialize the `MemberList` view model using the following parameters.

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `provider` | `ChatProvider` | n/a | A `ChatProvider` instance to be used inside this component. |
| `selectedChannelId` | `String` | n/a | The channel identifier used during a member query. |
| `fetchedEntities` | `NSFetchedResultsController` | n/a | The data type and query options used to populate the component. |
| `componentTheme` | `MemberListComponentTheme?` | The `MemberList` theme sourced from `ChatProvider`. | The configurable theme used for the component. If you provide a custom object, you must maintain it outside of `ChatProvider`. |

### View model

The view model is used to configure both the main `UIViewController` for the component, the member data, and lay out any sub-components.

To override core functionality, you can either subclass the view model or implement override blocks to replace specific key functionalities.

The default `MemberListComponentViewModel` is configured to display the users that are members of a specific channel, sorted by their presence status and name.

#### Component population

`ChatProvider` can be extended to return a configured `NSFetchedResultsController`, and this object ensures the component always displays the most recent data. `NSFetchedResultsController` can contain any type of stored data related to a member, and can be sorted and sectioned based on any member property.

#### Default example

By default, the list is populated by any user that's a member of the provided channel. The list is sorted based on the presence status and the name of the user.

```swift
open func userMembersFrom(
  channelId: String,
  excludingSender: Bool = false
) -> NSFetchedResultsController<ManagedEntities.Member> {

  let request = ManagedEntities.Member.membersBy(
    channelID: channelId,
    excludingUserId: excludingSender ? senderID : nil,
    onlyPresent: true
  )

  request.sortDescriptors = [
    NSSortDescriptor(key: "user.name", ascending: true)
  ]

  request.relationshipKeyPathsForPrefetching = ["user"]

  return fetchedResultsControllerProvider(
    fetchRequest: request,
    sectionKeyPath: nil,
    cacheName: nil
  )
}
```

You can create a view model composed of the above default `NSFetchedResultsController` and `MemberListComponentTheme` using this `ChatProvider` extension method:

```swift
open func memberListComponentViewModel(
  channelId: String,
  customTheme: MemberListComponentTheme? = nil
) -> MemberListComponentViewModel<ModelData, ManagedEntities> {
  return MemberListComponentViewModel(
    provider: self,
    fetchedEntities: userMembersFrom(channelId: channelId),
    customTheme: customTheme ?? self.themeProvider.template.memberListComponent
  )
}
```

#### Properties

The `MemberList` view model provides outlets for the following properties after initialization:

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `provider` | `ChatProvider` | n/a | A `ChatProvider` instance to be used inside this component. |
| `fetchedEntities` | `NSFetchedResultsController` | n/a | The data type and query options used to populate the component. |
| `componentTheme` | `ChannelListComponentTheme` | The `ChannelList` theme sourced from `ChatProvider`. | The configurable theme used for the component. |
| `selectedChannelId` | `String` | n/a | The channel identifier used during the member query. |
| `leftBarButtonNavigationItems` | `(UIViewController, MemberListComponentViewModel) -> [UIBarButtonItem]` | `nil` | Views that appear on the left side of the navigation bar. |
| `rightBarButtonNavigationItems` | `(UIViewController, MemberListComponentViewModel) -> [UIBarButtonItem]` | `nil` | Views that appear on the right side of the navigation bar. |
| `customNavigationTitleView` | `(MemberListComponentViewModel) -> UIView?` | `nil` | The custom view that's used inside the navigation bar title. |
| `customNavigationTitleString` | `(MemberListComponentViewModel) -> AnyPublisher<String?, Never>` | `nil` | The custom `String` publisher that's used inside the navigation bar title if a view isn't provided. |
| `customMemberCellViewModel` | `(MemberListComponentViewModel, Member) -> MemberCellComponentViewModel` | `nil` | The cell view model that's used instead of the default model found inside the theme. |
| `configureFetchedResultsController` | `(NSFetchedResultsController) -> Void` | `nil` | The outlet to customize the member data query before the data is fetched. |

You can configure the message items inside the list with the data found inside `ManagedMemberViewModel`:

```swift
public protocol ManagedMemberViewModel {
  associatedtype Entity: ManagedChatMember
  associatedtype ChannelViewModel: ManagedChannelViewModel
  associatedtype UserViewModel: ManagedUserViewModel

  var managedObjectId: NSManagedObjectID { get }

  var isPresentPublisher: AnyPublisher<Bool, Never> { get }

  var channelViewModel: ChannelViewModel { get }
  var userViewModel: UserViewModel { get }
}
```

### Actions

This component supports the following UI interactions:

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `componentDidLoad` | `(UIViewController, MemberListComponentViewModel) -> Void` | `nil` | An action called when the component view is loaded. |
| `componentWillAppear` | `(UIViewController, MemberListComponentViewModel) -> Void` | `nil` | An action called when the component view appears on the screen. |
| `componentWillDisappear` | `(UIViewController, MemberListComponentViewModel) -> Void` | `nil` | An action called when the component view disappears from the screen. |
| `didSelectMember` | `(UIViewController, MemberListComponentViewModel, ChatMember) -> Void` | `nil` | An action called when a user taps a list item. |

#### Example

The following example shows how an action can be configured after creating a view model.

`componentDidLoad` is called when the component view first loads. The following example shows how to call PubNub HereNow presence and automatically store the response in the local database.

```swift
let memberViewModel = provider
  .memberListComponentViewModel(channelId: "stored-channel-id")

memberViewModel.componentDidLoad = { controller, viewModel in
  viewModel.provider.dataProvider.syncHereNow(
    .init(channels: [viewModel.selectedChannel.pubnubId]),
    completion: nil
  )
}
```

## MessageList

`UIViewController` that displays a list of stored message objects. It's primarily used to fetch historical messages for a channel, including user names, avatars, times of sending, and rich message types (links, images).

### Parameters

You can initialize the `MessageList` view model using the following parameters.

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `provider` | `ChatProvider` | n/a | A `ChatProvider` instance to be used inside this component. |
| `author` | `User` | n/a | The `User` that represents the current application user. |
| `selectedChannel` | `Channel` | n/a | The channel of the displayed messages. |
| `fetchedEntities` | `NSFetchedResultsController` | n/a | The data type and query options used to populate the component. |
| `componentTheme` | `MessageListComponentTheme?` | The `MessageList` theme sourced from `ChatProvider`. | The configurable theme used for the component. If you provide a custom object, you must maintain it outside of `ChatProvider`. |

### View model

The view model is used to configure both the main `UIViewController` for the component, the member data, and lay out any sub-components.

To override core functionality, you can either subclass the view model or implement override blocks to replace specific key functionalities.

The default `MessageListComponentViewModel` is configured to display the messages of a particular channel sorted by the time a given message was sent.

#### Component population

`ChatProvider` can be extended to return a configured `NSFetchedResultsController`, and this object ensures the component always displays the most recent data. `NSFetchedResultsController` can contain any type of stored data related to a message, and can be sorted and sectioned based on any message property.

#### Default example

By default, the list is populated by any channel of which the current user is a member. The list will be divided into multiple sections based on the channel type, and each section is sorted by the name of the channel.

```swift
open func messagesFrom(
  pubnubChannelId: String
) -> NSFetchedResultsController<ManagedEntities.Message> {

  let request = ManagedEntities.Message.messagesBy(pubnubChannelId: pubnubChannelId)

  request.sortDescriptors = [
    NSSortDescriptor(key: "dateCreated", ascending: true)
  ]

  request.relationshipKeyPathsForPrefetching = ["sender"]

  return fetchedResultsControllerProvider(
    fetchRequest: request,
    sectionKeyPath: nil,
    cacheName: nil
  )
}
```

You can create a view model composed of the above default `NSFetchedResultsController` and `MessageListComponentTheme` using this `ChatProvider` extension method:

```swift
open func messageListComponentViewModel(
  for pubnubChannelId: String,
  customTheme: MessageListComponentTheme? = nil
) throws -> MessageListComponentViewModel<ModelData, ManagedEntities> {
  let sender = try fetchSender()

  guard let channel = try fetchChannel(byPubNubId: pubnubChannelId) else {
    throw ChatError.missingRequiredData
  }

  return MessageListComponentViewModel(
    provider: self,
    sender: sender,
    selectedChannel: channel,
    fetchedMessages: messagesFrom(pubnubChannelId: pubnubChannelId),
    componentTheme: customTheme ?? themeProvider.template.messageListComponent
  )
}
```

:::note Channel ID storage
You must store the channel matching `pubnubChannelId` locally before calling the `messageListComponentViewModel()` method.
:::

#### Properties

You can configure the `MessageList` view model using the following parameters.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `provider` | `ChatProvider` | n/a | A `ChatProvider` instance to be used inside this component. |
| `author` | `User` |  | The `User` that represents the current application user. |
| `selectedChannel` | `Channel` | n/a | The channel of the displayed message. |
| `fetchedEntities` | `NSFetchedResultsController` | n/a | The data type and query options used to populate the component. |
| `componentTheme` | `MessageListComponentTheme` | The `MessageList` theme sourced from `ChatProvider`. | The configurable theme used for the component. |
| `leftBarButtonNavigationItems` | `(UIViewController, MessageListComponentViewModel) -> [UIBarButtonItem]` | `nil` | Views that appear on the left side of the navigation bar. |
| `rightBarButtonNavigationItems` | `(UIViewController, MessageListComponentViewModel) -> [UIBarButtonItem]` | `nil` | Views that appear on the right side of the navigation bar. |
| `customNavigationTitleView` | `(MessageListComponentViewModel) -> UIView?` | `nil` | The custom view that's used inside the navigation bar title. |
| `customNavigationTitleString` | `(MessageListComponentViewModel) -> AnyPublisher<String?, Never>` | `nil` | The custom `String` publisher that's used inside the navigation bar title if a view isn't provided. |
| `configureFetchedResultsController` | `(NSFetchedResultsController) -> Void` | `nil` | The outlet to customize the message data query before the data is fetched. |

You can configure the message items inside the list with the data found inside `ManagedChannelViewModel`:

```swift
public protocol ManagedMessageViewModel: AnyObject {
  associatedtype Entity: ManagedChatMessage
  associatedtype ChannelViewModel: ManagedChannelViewModel
  associatedtype UserViewModel: ManagedUserViewModel
  associatedtype MessageActionModel: ManagedMessageActionViewModel & Hashable
  
  var pubnubId: Timetoken { get }
  var managedObjectId: NSManagedObjectID { get }
  
  var text: String { get }
  
  var messageContentTypePublisher: AnyPublisher<String, Never> { get }
  var messageContentPublisher: AnyPublisher<Data, Never> { get }
  
  var messageTextPublisher: AnyPublisher<String, Never> { get }
  var messageCustomPublisher: AnyPublisher<Data, Never> { get }
  
  var messageDateCreatedPublisher: AnyPublisher<Date, Never> { get }
  
  var messageActionsPublisher: AnyPublisher<Set<MessageActionModel>, Never> { get }
  var messageActions: Set<MessageActionModel> { get }
  
  var userViewModel: UserViewModel { get }
  var channelViewModel: ChannelViewModel { get }
  var messageActionViewModels: Set<MessageActionModel> { get }
}
```

### Actions

This component supports the following UI interactions:

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `componentDidLoad` | `(UIViewController, MemberListComponentViewModel) -> Void` | `nil` | Action called when the component view is loaded into memory. Maps to `viewDidLoad()` of the component view. |
| `componentWillAppear` | `(UIViewController, MemberListComponentViewModel) -> Void` | `nil` | Action called when the component view is about to be added to a view hierarchy. Maps to `viewWillAppear(_:)` of the component view. |
| `componentWillDisappear` | `(UIViewController, MemberListComponentViewModel) -> Void` | `nil` | Action called when the component view is removed from the view hierarchy. Maps to `viewWillDisappear(_:)` of the component view. |

### Message Reactions

The [Message reactions](https://www.pubnub.com/docs/chat/community-supported/ios/message-reactions) implementation is an integral part of the [MessageList](#messagelist) component and allows you to react to messages in chat apps by long-tapping a message and choosing a reaction from a Reaction Picker or short-tapping a reaction already added by someone else.

This feature is based on the `MessageReactionListComponent` class that contains six predefined `MessageReactionButtonComponent` items (emojis) laid down horizontally. Appearance of each `MessageReactionButtonComponent` is defined by `MessageReactionComponent`.

:::note Component dependency
Unlike the other components that can be used as standalone view controllers, message reactions are tightly bound to the `MessageList` component. The `MessageReactionListComponent` class is a sub-view of `MessageListItemCell` which basically means that each message has its own `MessageReactionListComponent`.
:::

#### View model

The appearance of the message reaction list inside every `MessageListItemCell` is automatically controlled by actions count. If its value is greater than zero, the reaction list view becomes visible. There's currently no other way to configure or initialize message reactions in this scope.

##### Component population

The `configure()` method that is responsible for configuring the reaction list is called on tapping a message if reactions on messages are enabled (the `enableReactions` flag is set to `true`). This method contains more granular operations like hiding or showing particular reaction, handling tap gestures.

```swift
open func configure<Message>(
  _ message: Message,
  currentUserId: String,
  onMessageActionTap: ((MessageReactionButtonComponent?, Message, (() -> Void)?) -> Void)?
) where Message : ManagedMessageViewModel {
  configure(
    [
      thumbsUpReactionView,
      redHeartReactionView,
      faceWithTearsOfJoyReactionView,
      astonishedFaceReactionView,
      cryingFaceReactionView,
      fireReactionView
    ],
    message: message,
    currentUserId: currentUserId,
    onMessageActionTap: onMessageActionTap
  )
}
```

## MessageInput

`UIView` that can be used to display an interactive `UITextView` with an optional, customizable `UIButton` used for sending the `String` input.

:::note Component dependency
Unlike the other components that can be used as standalone view controllers, the message input should be treated as a sub-component. It's added as a sub-view or `accessoryInput` view of the `UIViewController` of another component.
:::

### Parameters

`MessageInputComponent` is the base class for the component. It can be sub-classed to add additional functionality or to include a third-party input bar. You can initialize the component through `MessageInputComponentViewModel`.

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `provider` | `ChatProvider` | n/a | `ChatProvider` instance to be used inside this component. |
| `selectedChannel` | `Channel` | n/a | Channel used when sending messages. |
| `componentTheme` | `MessageInputComponentTheme?` | The `MessageInput` theme sourced from `ChatProvider`. | The configurable theme used for the component. If you provide a custom object, you must maintain it outside of `ChatProvider`. |

### View model

The view model is used to configure both the main `UIView` for the component, the configuration of any sub-views, and the interaction between the `MessageInputComponent` and `MessageListComponent`.

To override core functionality, you can either subclass the view model or implement override blocks to replace specific key functionalities.

The default `MessageInputComponentViewModel` is configured based on the configurations found inside `MessageInputComponentTheme`.

#### Properties

The `MessageInput` view model provides outlets for the following properties after initialization:

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `provider` | `ChatProvider` | n/a | `ChatProvider` instance to be used inside this component. |
| `selectedChannel` | `Channel` | n/a | Channel used when sending messages. |
| `componentTheme` | `@Published MessageInputComponentTheme` | The `MessageInput` theme sourced from `ChatProvider`. | The configurable theme used for the component. |
| `typingMemberIds` | `@Published Set<String>` | `[]` | `Set` of member PubNub identifiers that are currently typing on the channel. An empty `Set` means no one is currently typing. |

### Actions

This component supports the following UI interactions.

| Name | Type | Default value | Description |
| --- | --- | --- | --- |
| `messageWillSend` | `(MessageInputComponentViewModel<ModelData, ManagedEntities>, ChatMessage<ModelData>) -> ChatMessage<ModelData>` | `nil` | Closure that's called before sending a message. Allows you to mutate the message that is sent. |
| `messageDidSend` | `(MessageInputComponentViewModel<ModelData, ManagedEntities>, ChatMessage<ModelData>, Future<ChatMessage<ModelData>, Error>) -> Void` | `nil` | Closure that's called after the message was sent. You can use `Future` to track the transmission of the message and handle any `Error` that might be returned. |

#### Example

Let's see how to configure an action after creating a view model.

`messageWillSend` is called when the user is attempting to send a new message. The following example shows how to store the message before sending so it appears on the `MessageList` component faster:

```swift
let messageInputViewModel = try provider
  .messageInputComponentViewModel(channelId: "stored-channel-id")

messageInputViewModel.messageWillSend = { viewModel, message in

  viewModel.provider.dataProvider.load(messages: [message])

  return message
}
```

### Offline message behavior

When you send a message and the network connection is lost, the message won't be visible in the message input anymore upon tapping the `Send` button and other chat users won’t see it on the message list. The local database won’t store the message and it won’t reach the server either (the [publish method](https://www.pubnub.com/docs/sdks/swift/api-reference/publish-and-subscribe#methods) from the Swift SDK will fail).

However, you can monitor all messages for their status by configuring the [messageDidSend UI action](#actions-3). This closure is called after sending the message. You can use the `Future` object to track the transmission of the message and handle any returned errors to notify the sender about the message failure by presenting an error as a toast message or a popup.

```swift
messageListViewModel.messageInputViewModel.messageDidSend = { viewModel, message, future in
  future.sink(receiveCompletion: { completion in
    if case let .failure(error) = completion {
    }
  }, receiveValue: { messageSent in
  }).store(in: &cancellables)
}
```