Skip to content

Customizing Components

The SwiftUI SDK components are fully customizable and interchangeable through the ViewFactory protocol that holds all the reusable views of the SDK. You can find the full reference of the ViewFactory protocol here.

View Factory

The ViewFactory protocol defines the swappable views of the chat experience. There are default implementations for all the views used in the SDK. If you want to customize a view, you will need to implement the ViewFactory protocol, but only implement the view you want to swap.

In most cases, your custom view only needs to conform to the standard SwiftUI View protocol and provide a body property, there's no need to implement additional lifecycle methods or protocols. The only exception is when customizing the navigation bar, where your custom view must conform to SwiftUI's ToolbarContent protocol.

Options-Based API

All ViewFactory methods accept a single Options struct instead of individual parameters. Each Options struct groups together all the data and callbacks needed to create that view. This design makes it easy to add new parameters in future releases without breaking your existing implementations.

For example, if you want to change the default user avatar view that is used in multiple views, you implement the makeUserAvatarView(options:) slot in the ViewFactory protocol:

class CustomFactory: ViewFactory {

    @Injected(\.chatClient) public var chatClient

    public var styles = RegularStyles()

    private init() {}

    public static let shared = CustomFactory()

    func makeUserAvatarView(options: UserAvatarViewOptions) -> some View {
        CustomUserAvatarView(
            user: options.user,
            size: options.size
        )
    }
}

Afterwards, you need to inject the CustomFactory in your view. To do this, you pass the newly created factory to the ChatChannelListView. If you are only using the ChatChannelView, you can pass the factory to it instead.

var body: some Scene {
    WindowGroup {
        ChatChannelListView(viewFactory: CustomFactory.shared)
    }
}

And that's everything needed to provide custom views for the SwiftUI SDK.

Tip:

The ViewFactory protocol is generic over the views that it can replace, and there is no usage of AnyView. This allows SwiftUI to compute the diffing of the views faster and more accurately without any performance overhead.

Styles

In addition to ViewFactory, the SDK provides a Styles protocol that controls the overall look and layout of key UI surfaces. Every ViewFactory has a styles property that you can set to control styling independently from view swapping.

The two built-in implementations are:

Style Composer placement Look
RegularStyles Docked at the bottom Classic chat UI
LiquidGlassStyles Floating above keyboard iOS 26 Liquid Glass

To use a particular style, set the styles property on your factory:

class CustomFactory: ViewFactory {

    @Injected(\.chatClient) public var chatClient

    public var styles = LiquidGlassStyles()

    private init() {}

    public static let shared = CustomFactory()
}

You can also create your own custom Styles implementation to override specific parts:

class CustomStyles: Styles {
    var composerPlacement: ComposerPlacement = .docked

    func makeComposerInputViewModifier(options: ComposerInputModifierOptions) -> some ViewModifier {
        MyInputModifier()
    }

    func makeComposerButtonViewModifier(options: ComposerButtonModifierOptions) -> some ViewModifier {
        MyButtonModifier()
    }

    func makeScrollToBottomButtonModifier(options: ScrollToBottomButtonModifierOptions) -> some ViewModifier {
        MyScrollToBottomModifier()
    }

    func makeComposerViewModifier(options: ComposerViewModifierOptions) -> some ViewModifier {
        MyComposerBackgroundModifier()
    }

    func makeSuggestionsContainerModifier(options: SuggestionsContainerModifierOptions) -> some ViewModifier {
        MySuggestionsModifier()
    }

    func makeSearchableModifier(options: SearchableModifierOptions) -> some ViewModifier {
        DefaultSearchableModifier(
            searchText: options.searchText,
            prompt: "Search"
        )
    }
}

The full list of Styles hooks includes:

  • makeComposerInputViewModifier – modifier applied to the composer text input area.
  • makeComposerButtonViewModifier – modifier applied to composer action buttons.
  • makeScrollToBottomButtonModifier – modifier applied to the scroll-to-bottom button.
  • makeComposerViewModifier – modifier applied to the whole composer container.
  • makeSuggestionsContainerModifier – modifier applied to the suggestions overlay.
  • makeChannelListContentModifier – modifier for the full channel list content area.
  • makeChannelListModifier – modifier applied to the channel list itself.
  • makeSearchableModifier – modifier applied to built-in search UI such as the channel list search.
  • makeMessageListModifier – modifier applied to the message list.
  • makeMessageListContainerModifier – modifier applied to the message list container.
  • makeMessageViewModifier – modifier applied to each individual message bubble.
  • makeMessageAttachmentsViewModifier – modifier applied to the outer view that wraps a message's attachment stack.
  • makeMessageAttachmentItemViewModifier – modifier applied to file, voice, link, image, video, and quoted-message reference bubbles. For quoted-message references, MessageAttachmentItemViewModifierOptions.attachmentType is nil.
  • makeBouncedMessageActionsModifier – modifier for bounced-message action UI.

Complete ViewFactory reference

Below is the full list of ViewFactory hooks, grouped by area. Expand a section to see all available slots. You can also browse the full source here.

Avatars
Method Description
func makeChannelAvatarView(options: ChannelAvatarViewOptions) -> ChannelAvatarViewType Avatar shown in the channel list, header, and search results
func makeUserAvatarView(options: UserAvatarViewOptions) -> UserAvatarViewType User avatar used throughout the SDK
Channel List
Method Description
func makeChannelListHeaderViewModifier(options: ChannelListHeaderViewModifierOptions) -> HeaderViewModifier Navigation header view modifier for the channel list
func makeEmptyChannelsView(options: EmptyChannelsViewOptions) -> EmptyChannels Empty state view when there are no channels
func makeLoadingView(options: LoadingViewOptions) -> LoadingContent Loading state view for the channel list
func makeChannelListItem(options: ChannelListItemOptions<ChannelDestination>) -> ChannelListItemType View for each channel row in the list
func makeChannelListBackground(options: ChannelListBackgroundOptions) -> ChannelListBackground Background view for the channel list
func makeChannelListItemBackground(options: ChannelListItemBackgroundOptions) -> ChannelListItemBackground Background view for each channel list item
func makeChannelListDividerItem(options: ChannelListDividerItemOptions) -> ChannelListDividerItem Divider between channel list items
func makeMoreChannelActionsView(options: MoreChannelActionsViewOptions) -> MoreActionsView Additional channel actions popup
func makeTrailingSwipeActionsView(options: TrailingSwipeActionsViewOptions) -> TrailingSwipeActionsViewType Trailing swipe actions for a channel row
func makeLeadingSwipeActionsView(options: LeadingSwipeActionsViewOptions) -> LeadingSwipeActionsViewType Leading swipe actions for a channel row
func makeChannelListTopView(options: ChannelListTopViewOptions) -> ChannelListTopViewType View shown above the channel list
func makeChannelListFooterView(options: ChannelListFooterViewOptions) -> ChannelListFooterViewType Footer view for the channel list
func makeChannelListStickyFooterView(options: ChannelListStickyFooterViewOptions) -> ChannelListStickyFooterViewType Sticky footer view for the channel list
func makeSearchResultsView(options: SearchResultsViewOptions) -> ChannelListSearchResultsViewType Container view for channel/message search results
func makeChannelListSearchResultItem(options: ChannelListSearchResultItemOptions<ChannelDestination>) -> ChannelListSearchResultItem View for each search result item
Channel And Message List
Method Description
func makeChannelDestination(options: ChannelDestinationOptions) -> @MainActor (ChannelSelectionInfo) -> ChannelDestination Destination view when navigating to a channel
func makeMessageThreadDestination(options: MessageThreadDestinationOptions) -> @MainActor (ChatChannel, ChatMessage) -> MessageThreadDestination Destination view when navigating to a message thread
func makeEmptyMessagesView(options: EmptyMessagesViewOptions) -> EmptyMessagesViewType Empty state view when there are no messages
func makeChannelHeaderViewModifier(options: ChannelHeaderViewModifierOptions) -> ChatHeaderViewModifier Navigation header modifier for the channel view
func makeChannelBarsVisibilityViewModifier(options: ChannelBarsVisibilityViewModifierOptions) -> ChangeBarsVisibilityModifier Controls top/bottom bars visibility
func makeChannelLoadingView(options: ChannelLoadingViewOptions) -> ChannelLoadingViewType Loading state for ChatChannelView
func makeMessageThreadHeaderViewModifier(options: MessageThreadHeaderViewModifierOptions) -> ThreadHeaderViewModifier Navigation header modifier for the thread view
func makeMessageListBackground(options: MessageListBackgroundOptions) -> MessageListBackground Background view for the message list
func makeMessageItemView(options: MessageItemViewOptions) -> MessageItemViewType Container view for each message in the list
func makeMessageTextView(options: MessageTextViewOptions) -> MessageTextViewType Text content view for a message
func makeMessageDateView(options: MessageDateViewOptions) -> MessageDateViewType Date label shown below/inside a message
func makeMessageAuthorAndDateView(options: MessageAuthorAndDateViewOptions) -> MessageAuthorAndDateViewType Author name and date header layout
func makeMessageTopView(options: MessageTopViewOptions) -> MessageTopViewType View shown above the message bubble
func makeLastInGroupHeaderView(options: LastInGroupHeaderViewOptions) -> LastInGroupHeaderView Header view for the last message in a group
func makeMessageAttachmentsView(options: MessageAttachmentsViewOptions) -> MessageAttachmentsViewType Container view for message attachments
func makeImageAttachmentView(options: ImageAttachmentViewOptions) -> ImageAttachmentViewType Image attachment view
func makeGiphyAttachmentView(options: GiphyAttachmentViewOptions) -> GiphyAttachmentViewType Giphy attachment view
func makeLinkAttachmentView(options: LinkAttachmentViewOptions) -> LinkAttachmentViewType Link preview attachment view
func makeFileAttachmentView(options: FileAttachmentViewOptions) -> FileAttachmentViewType File attachment view
func makeVideoAttachmentView(options: VideoAttachmentViewOptions) -> VideoAttachmentViewType Video attachment view
func makeMediaViewer(options: MediaViewerOptions) -> MediaViewerType Full-screen media gallery viewer
func makeMediaViewerToolbarModifier(options: MediaViewerToolbarModifierOptions) -> MediaViewerToolbarModifierType Toolbar modifier for the media gallery viewer
func makeMediaViewerFooterView(options: MediaViewerFooterViewOptions) -> MediaViewerFooterViewType Footer for the media gallery viewer
func makeDeletedMessageView(options: DeletedMessageViewOptions) -> DeletedMessageViewType View for deleted messages
func makeSystemMessageView(options: SystemMessageViewOptions) -> SystemMessageViewType View for system messages
func makeEmojiTextView(options: EmojiTextViewOptions) -> EmojiTextViewType Text view that renders emoji
func makeVoiceRecordingView(options: VoiceRecordingViewOptions) -> VoiceRecordingViewType Voice recording attachment view
func makeCustomAttachmentViewType(options: CustomAttachmentViewTypeOptions) -> CustomAttachmentViewType View for custom attachment types
func makeScrollToBottomButton(options: ScrollToBottomButtonOptions) -> ScrollToBottomButtonType Button to scroll to the latest message
func makeDateIndicatorView(options: DateIndicatorViewOptions) -> DateIndicatorViewType Date overlay/separator indicator
func makeMessageListDateIndicator(options: MessageListDateIndicatorViewOptions) -> MessageListDateIndicatorViewType Date indicator shown while scrolling
func makeInlineTypingIndicatorView(options: TypingIndicatorViewOptions) -> InlineTypingIndicatorViewType Inline typing indicator shown in the message list
func makeSubtitleTypingIndicatorView(options: SubtitleTypingIndicatorViewOptions) -> SubtitleTypingIndicatorViewType Subtitle typing indicator in the navigation bar
func makeGiphyBadgeViewType(options: GiphyBadgeViewTypeOptions) -> GiphyBadgeViewType Badge overlay for Giphy messages
func makeMessageRepliesView(options: MessageRepliesViewOptions) -> MessageRepliesViewType Thread replies count button below a message
Composer And Suggestions
Method Description
func makeMessageComposerViewType(options: MessageComposerViewTypeOptions) -> MessageComposerViewType The full message composer component
func makeLeadingComposerView(options: LeadingComposerViewOptions) -> LeadingComposerViewType Leading view of the composer (e.g. add attachment button)
func makeComposerInputView(options: ComposerInputViewOptions) -> ComposerInputViewType Input area of the composer
func makeComposerTextInputView(options: ComposerTextInputViewOptions) -> ComposerTextInputViewType Text input field inside the composer
func makeComposerInputTrailingView(options: ComposerInputTrailingViewOptions) -> ComposerInputTrailingViewType Trailing view inside the composer input (e.g. emoji button)
func makeTrailingComposerView(options: TrailingComposerViewOptions) -> TrailingComposerViewType Trailing view of the composer (e.g. send button)
func makeSendMessageButton(options: SendMessageButtonOptions) -> SendMessageButtonType Send message button
func makeConfirmEditButton(options: ConfirmEditButtonOptions) -> ConfirmEditButtonType Confirm button when editing a message
func makeComposerVoiceRecordingInputView(options: ComposerVoiceRecordingInputViewOptions) -> ComposerVoiceRecordingInputViewType Voice recording UI inside the composer
func makeAttachmentPickerView(options: AttachmentPickerViewOptions) -> AttachmentPickerViewType Attachment picker container
func makeAttachmentCommandsPickerView(options: AttachmentCommandsPickerViewOptions) -> AttachmentCommandsPickerViewType Commands picker in the attachment area
func makeAttachmentTypePickerView(options: AttachmentTypePickerViewOptions) -> AttachmentTypePickerViewType Attachment type selector (photo, file, poll, etc.)
func makeAttachmentMediaPickerView(options: AttachmentMediaPickerViewOptions) -> AttachmentMediaPickerViewType Photo/video picker
func makeAttachmentFilePickerView(options: AttachmentFilePickerViewOptions) -> AttachmentFilePickerViewType File picker
func makeAttachmentCameraPickerView(options: AttachmentCameraPickerViewOptions) -> AttachmentCameraPickerViewType Camera picker
func makeCustomAttachmentPickerView(options: CustomAttachmentPickerViewOptions) -> CustomAttachmentPickerViewType Custom attachment picker
func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions) -> CustomAttachmentPreviewViewType Preview for custom attachments in the composer
func makeAttachmentPollPickerView(options: AttachmentPollPickerViewOptions) -> AttachmentPollPickerViewType Poll creation picker in the composer
func makeSendInChannelView(options: SendInChannelViewOptions) -> SendInChannelViewType "Send also in channel" toggle for thread replies
func makeSuggestionsContainerView(options: SuggestionsContainerViewOptions) -> SuggestionsContainerViewType Container for command/mention suggestions
func makeComposerQuotedMessageView(options: ComposerQuotedMessageViewOptions) -> ComposerQuotedMessageViewType Quoted message shown inside the composer
func makeQuotedMessageView(options: QuotedMessageViewOptions) -> QuotedMessageViewType Base quoted message view
func makeChatQuotedMessageView(options: ChatQuotedMessageViewOptions) -> ChatQuotedMessageViewType Quoted message shown in the message list
func makeComposerEditedMessageView(options: ComposerEditedMessageViewOptions) -> ComposerEditedMessageViewType Edited message shown inside the composer
func makeMessageAttachmentPreviewThumbnailView(options: MessageAttachmentPreviewViewOptions) -> MessageAttachmentPreviewViewType Thumbnail preview for attachments in the composer
func makeMessageAttachmentPreviewIconView(options: MessageAttachmentPreviewIconViewOptions) -> MessageAttachmentPreviewIconViewType Icon preview for attachments in the composer
Reactions, Actions, And Read State
Method Description
func makeMessageActionsView(options: MessageActionsViewOptions) -> MessageActionsViewType Message actions popup (copy, delete, edit, etc.)
func makeReactionsDetailView(options: ReactionsDetailViewOptions) -> ReactionsDetailViewType Detail view showing who reacted
func makeBottomReactionsView(options: ReactionsBottomViewOptions) -> ReactionsBottomViewType Reactions shown below a message
func makeMessageReactionView(options: MessageReactionViewOptions) -> MessageReactionViewType Single reaction view
func makeReactionsOverlayView(options: ReactionsOverlayViewOptions) -> ReactionsOverlayViewType Overlay shown when adding a reaction
func makeReactionsContentView(options: ReactionsContentViewOptions) -> ReactionsContentView Content view for the reactions picker
func makeMoreReactionsView(options: MoreReactionsViewOptions) -> MoreReactionsViewType "More reactions" button/view
func makeMessageReadIndicatorView(options: MessageReadIndicatorViewOptions) -> MessageReadIndicatorViewType Read indicator shown next to messages
func makeNewMessagesDividerView(options: NewMessagesDividerViewOptions) -> NewMessagesDividerType Divider indicating unread messages
func makeJumpToUnreadButtonOverlay(options: JumpToUnreadButtonOptions) -> JumpToUnreadButtonOverlayType Overlay modifier for the jump-to-unread button
func makeThreadRepliesDividerView(options: ThreadRepliesDividerViewOptions) -> ThreadRepliesDividerType Divider between parent message and thread replies
Polls
Method Description
func makePollView(options: PollViewOptions) -> PollViewType Poll attachment view in the message list
func makeAttachmentPollPickerView(options: AttachmentPollPickerViewOptions) -> AttachmentPollPickerViewType Poll creation view in the composer
Thread List
Method Description
func makeThreadDestination(options: ThreadDestinationOptions) -> @MainActor (ChatThread) -> ThreadDestination Destination view when navigating to a thread
func makeThreadListItem(options: ThreadListItemOptions<ThreadDestination>) -> ThreadListItemType View for each thread row in the thread list
func makeEmptyThreadsView(options: EmptyThreadsViewOptions) -> EmptyThreads Empty state view when there are no threads
func makeThreadListLoadingView(options: ThreadListLoadingViewOptions) -> ThreadListLoadingView Loading state view for the thread list
func makeThreadListContainerViewModifier(options: ThreadListContainerModifierOptions) -> ThreadListContainerModifier Modifier applied to the thread list container
func makeThreadListHeaderViewModifier(options: ThreadListHeaderViewModifierOptions) -> ThreadListHeaderViewModifier Navigation header modifier for the thread list
func makeThreadListHeaderView(options: ThreadListHeaderViewOptions) -> ThreadListHeaderView Header view shown at the top of the thread list
func makeThreadListFooterView(options: ThreadListFooterViewOptions) -> ThreadListFooterView Footer shown when loading more threads
func makeThreadListBackground(options: ThreadListBackgroundOptions) -> ThreadListBackground Background view for the thread list
func makeThreadListItemBackground(options: ThreadListItemBackgroundOptions) -> ThreadListItemBackground Background view for each thread list item
func makeThreadListDividerItem(options: ThreadListDividerItemOptions) -> ThreadListDividerItem Divider between thread list items
Member Add And Text Rendering
Method Description
func makeMemberAddView(options: MemberAddViewOptions) -> MemberAddViewType Add members view for a channel
func makeAttachmentTextView(options: AttachmentTextViewOptions) -> AttachmentTextViewType Text preview for attachments
func makeStreamTextView(options: StreamTextViewOptions) -> StreamTextViewType Custom text view for message rendering

Accessing Dependencies

When building your own view components and you want to use the dependencies of the SwiftUI SDK, you can access them by using the @Injected(\.keyPath) property wrapper:

@Injected(\.chatClient) var chatClient
@Injected(\.colors) var colors
@Injected(\.fonts) var fonts
@Injected(\.images) var images
@Injected(\.utils) var utils
Note:

The @Injected property wrapper works similarly to the @Environment in SwiftUI, but it also allows access to the dependencies in non-view related code. This approach is inspired by the following Dependency Injection article by Antoine van der Lee.

Extending with Custom Types

In some cases, you might also need to extend the @Injected property wrapper with your own types. For example, you may want to be able to access your custom types like this:

@Injected(\.customType) var customType

In order to achieve this, you first need to define your own InjectionKey, and define it's currentValue, which basically creates the new instance of your type.

class CustomType {
	// your custom logic here
}

struct CustomInjectionKey: InjectionKey {
    static var currentValue: CustomType = CustomType()
}

Next, you need to extend our InjectedValues with your own custom type, by defining its getter and setter.

extension InjectedValues {
    /// Provides access to the `CustomType` instance in the views and view models.
    var customType: CustomType {
        get {
            Self[CustomInjectionKey.self]
        }
        set {
            Self[CustomInjectionKey.self] = newValue
        }
    }
}

With these few simple steps, you can now access your custom type in both your app code and in your custom implementations of the views used throughout the SDK.

Build Time Improvements

If you are customizing many view slots (over 15) from the SDK, and you have many generics in your codebase, it's a good idea to explicitly specify the types of your custom views with typealias. This will improve the build time of your project.

For example, in our ViewFactory, the associatedType for creating the empty channels view is EmptyChannels:

associatedtype EmptyChannels: View
func makeEmptyChannelsView(options: EmptyChannelsViewOptions) -> EmptyChannels

In order to improve the build time, you would need to specify the associated type with a typealias, like this:

typealias EmptyChannels = CustomEmptyChannelsView

Then, your custom implementation of the factory method makeEmptyChannelsView will look like the following:

public func makeEmptyChannelsView(options: EmptyChannelsViewOptions) -> CustomEmptyChannelsView {
    CustomEmptyChannelsView()
}

You can find all the associated types we use in the ViewFactory here.