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.
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.
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 linesYou 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:
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
:
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 linesActions
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.
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.
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.
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 linesYou 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:
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
:
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.
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.
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 linesYou 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 linesChannel 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
:
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 linesActions
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 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 linesMessageInput
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
.
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:
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)
}