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

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 made against stored channel data.

Parameters

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

NameTypeDefault valueDescription
providerChatProvidern/aThe ChatProvider instance to be used inside this component.
fetchedEntitiesNSFetchedResultsControllern/aThe data type and query options used to populate the component.
componentThemeChannelListComponentTheme?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.

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,
show all 18 lines

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

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:

NameTypeDefault valueDescription
providerChatProvidern/aA ChatProvider instance to be used inside this component.
fetchedEntitiesNSFetchedResultsControllern/aThe data type and query options used to populate the component.
componentThemeChannelListComponentThemeThe ChannelList theme sourced from ChatProvider.The configurable theme used for the component.
leftBarButtonNavigationItems(UIViewController, ChannelListComponentViewModel) -> [UIBarButtonItem]nilViews that appear on the left side of the navigation bar.
rightBarButtonNavigationItems(UIViewController, ChannelListComponentViewModel) -> [UIBarButtonItem]nilViews that appear on the right side of the navigation bar.
customNavigationTitleView(ChannelListComponentViewModel) -> UIView?nilThe custom view that's used inside the navigation bar title.
customNavigationTitleString(ChannelListComponentViewModel) -> AnyPublisher<String?, Never>nilThe custom String publisher that's used inside the navigation bar title if a view isn't provided.
customChannelCellViewModel(ChannelListComponentViewModel, Channel) -> ChannelCellComponentViewModelnilThe cell view model that's be used instead of the default model found inside the theme.
configureFetchedResultsController(NSFetchedResultsController) -> VoidnilAn 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:

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 }
show all 21 lines

Actions

This component supports the following UI interactions:

NameTypeDefault valueDescription
componentDidLoad(UIViewController, ChannelListComponentViewModel) -> VoidnilAn action called when the component view is loaded.
componentWillAppear(UIViewController, ChannelListComponentViewModel) -> VoidnilAn action called when the component view appears on the screen.
componentWillDisappear(UIViewController, ChannelListComponentViewModel) -> VoidnilAn action called when the component view disappears from the screen.
didSelectChannel(UIViewController, ChannelListComponentViewModel, ChatChannel) -> VoidDefault MessageList componentAn 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.

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 made against stored member data.

Parameters

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

NameTypeDefault valueDescription
providerChatProvidern/aA ChatProvider instance to be used inside this component.
selectedChannelIdStringn/aThe channel identifier used during a member query.
fetchedEntitiesNSFetchedResultsControllern/aThe data type and query options used to populate the component.
componentThemeMemberListComponentTheme?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.

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)
]

show all 23 lines

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

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:

NameTypeDefault valueDescription
providerChatProvidern/aA ChatProvider instance to be used inside this component.
fetchedEntitiesNSFetchedResultsControllern/aThe data type and query options used to populate the component.
componentThemeChannelListComponentThemeThe ChannelList theme sourced from ChatProvider.The configurable theme used for the component.
selectedChannelIdStringn/aThe channel identifier used during the member query.
leftBarButtonNavigationItems(UIViewController, MemberListComponentViewModel) -> [UIBarButtonItem]nilViews that appear on the left side of the navigation bar.
rightBarButtonNavigationItems(UIViewController, MemberListComponentViewModel) -> [UIBarButtonItem]nilViews that appear on the right side of the navigation bar.
customNavigationTitleView(MemberListComponentViewModel) -> UIView?nilThe custom view that's used inside the navigation bar title.
customNavigationTitleString(MemberListComponentViewModel) -> AnyPublisher<String?, Never>nilThe custom String publisher that's used inside the navigation bar title if a view isn't provided.
customMemberCellViewModel(MemberListComponentViewModel, Member) -> MemberCellComponentViewModelnilThe cell view model that's used instead of the default model found inside the theme.
configureFetchedResultsController(NSFetchedResultsController) -> VoidnilThe 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:

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:

NameTypeDefault valueDescription
componentDidLoad(UIViewController, MemberListComponentViewModel) -> VoidnilAn action called when the component view is loaded.
componentWillAppear(UIViewController, MemberListComponentViewModel) -> VoidnilAn action called when the component view appears on the screen.
componentWillDisappear(UIViewController, MemberListComponentViewModel) -> VoidnilAn action called when the component view disappears from the screen.
didSelectMember(UIViewController, MemberListComponentViewModel, ChatMember) -> VoidnilAn 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.

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.

NameTypeDefault valueDescription
providerChatProvidern/aA ChatProvider instance to be used inside this component.
authorUsern/aThe User that represents the current application user.
selectedChannelChanneln/aThe channel of the displayed messages.
fetchedEntitiesNSFetchedResultsControllern/aThe data type and query options used to populate the component.
componentThemeMessageListComponentTheme?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.

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,
show all 18 lines

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

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),
show all 18 lines
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.

NameTypeDefaultDescription
providerChatProvidern/aA ChatProvider instance to be used inside this component.
authorUserThe User that represents the current application user.
selectedChannelChanneln/aThe channel of the displayed message.
fetchedEntitiesNSFetchedResultsControllern/aThe data type and query options used to populate the component.
componentThemeMessageListComponentThemeThe MessageList theme sourced from ChatProvider.The configurable theme used for the component.
leftBarButtonNavigationItems(UIViewController, MessageListComponentViewModel) -> [UIBarButtonItem]nilViews that appear on the left side of the navigation bar.
rightBarButtonNavigationItems(UIViewController, MessageListComponentViewModel) -> [UIBarButtonItem]nilViews that appear on the right side of the navigation bar.
customNavigationTitleView(MessageListComponentViewModel) -> UIView?nilThe custom view that's used inside the navigation bar title.
customNavigationTitleString(MessageListComponentViewModel) -> AnyPublisher<String?, Never>nilThe custom String publisher that's used inside the navigation bar title if a view isn't provided.
configureFetchedResultsController(NSFetchedResultsController) -> VoidnilThe 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:

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 }
show all 26 lines

Actions

This component supports the following UI interactions:

NameTypeDefault valueDescription
componentDidLoad(UIViewController, MemberListComponentViewModel) -> VoidnilAction called when the component view is loaded into memory. Maps to viewDidLoad() of the component view.
componentWillAppear(UIViewController, MemberListComponentViewModel) -> VoidnilAction called when the component view is about to be added to a view hierarchy. Maps to viewWillAppear(_:) of the component view.
componentWillDisappear(UIViewController, MemberListComponentViewModel) -> VoidnilAction called when the component view is removed from the view hierarchy. Maps to viewWillDisappear(_:) of the component view.

Message reactions

The Message reactions implementation is an integral part of the 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.

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.

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,
show all 19 lines

MessageInput

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

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.

NameTypeDefault valueDescription
providerChatProvidern/aChatProvider instance to be used inside this component.
selectedChannelChanneln/aChannel used when sending messages.
componentThemeMessageInputComponentTheme?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:

NameTypeDefaultDescription
providerChatProvidern/aChatProvider instance to be used inside this component.
selectedChannelChanneln/aChannel used when sending messages.
componentTheme@Published MessageInputComponentThemeThe 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.

NameTypeDefault valueDescription
messageWillSend(MessageInputComponentViewModel<ModelData, ManagedEntities>, ChatMessage<ModelData>) -> ChatMessage<ModelData>nilClosure 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>) -> VoidnilClosure 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:

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 from the Swift SDK will fail).

However, you can monitor all messages for their status by configuring the messageDidSend UI action. 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.

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