This is beta documentation for Stream Chat IOS SDK v5. For the latest stable version, see the latest version (v4) .

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.

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:

StyleComposer placementLook
RegularStylesDocked at the bottomClassic chat UI
LiquidGlassStylesFloating above keyboardiOS 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.
  • 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.

Channel List
MethodDescription
func makeChannelListHeaderViewModifier(options: ChannelListHeaderViewModifierOptions) -> HeaderViewModifierNavigation header view modifier for the channel list
func makeNoChannelsView(options: NoChannelsViewOptions) -> NoChannelsEmpty state view when there are no channels
func makeLoadingView(options: LoadingViewOptions) -> LoadingContentLoading state view for the channel list
func makeChannelListItem(options: ChannelListItemOptions<ChannelDestination>) -> ChannelListItemTypeView for each channel row in the list
func makeChannelAvatarView(options: ChannelAvatarViewOptions) -> ChannelAvatarViewTypeAvatar shown in the channel list, header, and search results
func makeChannelListBackground(options: ChannelListBackgroundOptions) -> ChannelListBackgroundBackground view for the channel list
func makeChannelListItemBackground(options: ChannelListItemBackgroundOptions) -> ChannelListItemBackgroundBackground view for each channel list item
func makeChannelListDividerItem(options: ChannelListDividerItemOptions) -> ChannelListDividerItemDivider between channel list items
func makeMoreChannelActionsView(options: MoreChannelActionsViewOptions) -> MoreActionsViewAdditional channel actions popup
func makeTrailingSwipeActionsView(options: TrailingSwipeActionsViewOptions) -> TrailingSwipeActionsViewTypeTrailing swipe actions for a channel row
func makeLeadingSwipeActionsView(options: LeadingSwipeActionsViewOptions) -> LeadingSwipeActionsViewTypeLeading swipe actions for a channel row
func makeChannelListTopView(options: ChannelListTopViewOptions) -> ChannelListTopViewTypeView shown above the channel list
func makeChannelListFooterView(options: ChannelListFooterViewOptions) -> ChannelListFooterViewTypeFooter view for the channel list
func makeChannelListStickyFooterView(options: ChannelListStickyFooterViewOptions) -> ChannelListStickyFooterViewTypeSticky footer view for the channel list
func makeSearchResultsView(options: SearchResultsViewOptions) -> ChannelListSearchResultsViewTypeContainer view for channel/message search results
func makeChannelListSearchResultItem(options: ChannelListSearchResultItemOptions<ChannelDestination>) -> ChannelListSearchResultItemView for each search result item
Channel And Message List
MethodDescription
func makeChannelDestination(options: ChannelDestinationOptions) -> @MainActor (ChannelSelectionInfo) -> ChannelDestinationDestination view when navigating to a channel
func makeMessageThreadDestination(options: MessageThreadDestinationOptions) -> @MainActor (ChatChannel, ChatMessage) -> MessageThreadDestinationDestination view when navigating to a message thread
func makeEmptyMessagesView(options: EmptyMessagesViewOptions) -> EmptyMessagesViewTypeEmpty state view when there are no messages
func makeUserAvatarView(options: UserAvatarViewOptions) -> UserAvatarViewTypeUser avatar used throughout the SDK
func makeChannelHeaderViewModifier(options: ChannelHeaderViewModifierOptions) -> ChatHeaderViewModifierNavigation header modifier for the channel view
func makeChannelBarsVisibilityViewModifier(options: ChannelBarsVisibilityViewModifierOptions) -> ChangeBarsVisibilityModifierControls top/bottom bars visibility
func makeChannelLoadingView(options: ChannelLoadingViewOptions) -> ChannelLoadingViewTypeLoading state for ChatChannelView
func makeMessageThreadHeaderViewModifier(options: MessageThreadHeaderViewModifierOptions) -> ThreadHeaderViewModifierNavigation header modifier for the thread view
func makeMessageListBackground(options: MessageListBackgroundOptions) -> MessageListBackgroundBackground view for the message list
func makeMessageItemView(options: MessageItemViewOptions) -> MessageItemViewTypeContainer view for each message in the list
func makeMessageTextView(options: MessageTextViewOptions) -> MessageTextViewTypeText content view for a message
func makeMessageDateView(options: MessageDateViewOptions) -> MessageDateViewTypeDate label shown below/inside a message
func makeMessageAuthorAndDateView(options: MessageAuthorAndDateViewOptions) -> MessageAuthorAndDateViewTypeAuthor name and date header layout
func makeMessageTopView(options: MessageTopViewOptions) -> MessageTopViewTypeView shown above the message bubble
func makeLastInGroupHeaderView(options: LastInGroupHeaderViewOptions) -> LastInGroupHeaderViewHeader view for the last message in a group
func makeMessageAttachmentsView(options: MessageAttachmentsViewOptions) -> MessageAttachmentsViewTypeContainer view for message attachments
func makeImageAttachmentView(options: ImageAttachmentViewOptions) -> ImageAttachmentViewTypeImage attachment view
func makeGiphyAttachmentView(options: GiphyAttachmentViewOptions) -> GiphyAttachmentViewTypeGiphy attachment view
func makeLinkAttachmentView(options: LinkAttachmentViewOptions) -> LinkAttachmentViewTypeLink preview attachment view
func makeFileAttachmentView(options: FileAttachmentViewOptions) -> FileAttachmentViewTypeFile attachment view
func makeVideoAttachmentView(options: VideoAttachmentViewOptions) -> VideoAttachmentViewTypeVideo attachment view
func makeMediaViewer(options: MediaViewerOptions) -> MediaViewerTypeFull-screen media gallery viewer
func makeMediaViewerHeader(options: MediaViewerHeaderOptions) -> MediaViewerHeaderTypeHeader for the media gallery viewer
func makeVideoPlayerView(options: VideoPlayerViewOptions) -> VideoPlayerViewTypeVideo player view
func makeVideoPlayerHeaderView(options: VideoPlayerHeaderViewOptions) -> VideoPlayerHeaderViewTypeHeader for the video player
func makeVideoPlayerFooterView(options: VideoPlayerFooterViewOptions) -> VideoPlayerFooterViewTypeFooter for the video player
func makeDeletedMessageView(options: DeletedMessageViewOptions) -> DeletedMessageViewTypeView for deleted messages
func makeSystemMessageView(options: SystemMessageViewOptions) -> SystemMessageViewTypeView for system messages
func makeEmojiTextView(options: EmojiTextViewOptions) -> EmojiTextViewTypeText view that renders emoji
func makeVoiceRecordingView(options: VoiceRecordingViewOptions) -> VoiceRecordingViewTypeVoice recording attachment view
func makeCustomAttachmentViewType(options: CustomAttachmentViewTypeOptions) -> CustomAttachmentViewTypeView for custom attachment types
func makeScrollToBottomButton(options: ScrollToBottomButtonOptions) -> ScrollToBottomButtonTypeButton to scroll to the latest message
func makeDateIndicatorView(options: DateIndicatorViewOptions) -> DateIndicatorViewTypeDate overlay/separator indicator
func makeMessageListDateIndicator(options: MessageListDateIndicatorViewOptions) -> MessageListDateIndicatorViewTypeDate indicator shown while scrolling
func makeInlineTypingIndicatorView(options: TypingIndicatorViewOptions) -> InlineTypingIndicatorViewTypeInline typing indicator shown in the message list
func makeSubtitleTypingIndicatorView(options: SubtitleTypingIndicatorViewOptions) -> SubtitleTypingIndicatorViewTypeSubtitle typing indicator in the navigation bar
func makeGiphyBadgeViewType(options: GiphyBadgeViewTypeOptions) -> GiphyBadgeViewTypeBadge overlay for Giphy messages
func makeMessageRepliesView(options: MessageRepliesViewOptions) -> MessageRepliesViewTypeThread replies count button below a message
Composer And Suggestions
MethodDescription
func makeMessageComposerViewType(options: MessageComposerViewTypeOptions) -> MessageComposerViewTypeThe full message composer component
func makeLeadingComposerView(options: LeadingComposerViewOptions) -> LeadingComposerViewTypeLeading view of the composer (e.g. add attachment button)
func makeComposerInputView(options: ComposerInputViewOptions) -> ComposerInputViewTypeInput area of the composer
func makeComposerTextInputView(options: ComposerTextInputViewOptions) -> ComposerTextInputViewTypeText input field inside the composer
func makeComposerInputTrailingView(options: ComposerInputTrailingViewOptions) -> ComposerInputTrailingViewTypeTrailing view inside the composer input (e.g. emoji button)
func makeTrailingComposerView(options: TrailingComposerViewOptions) -> TrailingComposerViewTypeTrailing view of the composer (e.g. send button)
func makeSendMessageButton(options: SendMessageButtonOptions) -> SendMessageButtonTypeSend message button
func makeConfirmEditButton(options: ConfirmEditButtonOptions) -> ConfirmEditButtonTypeConfirm button when editing a message
func makeComposerVoiceRecordingInputView(options: ComposerVoiceRecordingInputViewOptions) -> ComposerVoiceRecordingInputViewTypeVoice recording UI inside the composer
func makeAttachmentPickerView(options: AttachmentPickerViewOptions) -> AttachmentPickerViewTypeAttachment picker container
func makeAttachmentCommandsPickerView(options: AttachmentCommandsPickerViewOptions) -> AttachmentCommandsPickerViewTypeCommands picker in the attachment area
func makeAttachmentTypePickerView(options: AttachmentTypePickerViewOptions) -> AttachmentTypePickerViewTypeAttachment type selector (photo, file, poll, etc.)
func makeAttachmentMediaPickerView(options: AttachmentMediaPickerViewOptions) -> AttachmentMediaPickerViewTypePhoto/video picker
func makeAttachmentFilePickerView(options: AttachmentFilePickerViewOptions) -> AttachmentFilePickerViewTypeFile picker
func makeAttachmentCameraPickerView(options: AttachmentCameraPickerViewOptions) -> AttachmentCameraPickerViewTypeCamera picker
func makeCustomAttachmentPickerView(options: CustomAttachmentPickerViewOptions) -> CustomAttachmentPickerViewTypeCustom attachment picker
func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions) -> CustomAttachmentPreviewViewTypePreview for custom attachments in the composer
func makeAttachmentPollPickerView(options: AttachmentPollPickerViewOptions) -> AttachmentPollPickerViewTypePoll creation picker in the composer
func makeSendInChannelView(options: SendInChannelViewOptions) -> SendInChannelViewType"Send also in channel" toggle for thread replies
func makeSuggestionsContainerView(options: SuggestionsContainerViewOptions) -> SuggestionsContainerViewTypeContainer for command/mention suggestions
func makeComposerQuotedMessageView(options: ComposerQuotedMessageViewOptions) -> ComposerQuotedMessageViewTypeQuoted message shown inside the composer
func makeQuotedMessageView(options: QuotedMessageViewOptions) -> QuotedMessageViewTypeBase quoted message view
func makeChatQuotedMessageView(options: ChatQuotedMessageViewOptions) -> ChatQuotedMessageViewTypeQuoted message shown in the message list
func makeComposerEditedMessageView(options: ComposerEditedMessageViewOptions) -> ComposerEditedMessageViewTypeEdited message shown inside the composer
func makeMessageAttachmentPreviewThumbnailView(options: MessageAttachmentPreviewViewOptions) -> MessageAttachmentPreviewViewTypeThumbnail preview for attachments in the composer
func makeMessageAttachmentPreviewIconView(options: MessageAttachmentPreviewIconViewOptions) -> MessageAttachmentPreviewIconViewTypeIcon preview for attachments in the composer
Reactions, Actions, And Read State
MethodDescription
func makeMessageActionsView(options: MessageActionsViewOptions) -> MessageActionsViewTypeMessage actions popup (copy, delete, edit, etc.)
func makeReactionsDetailView(options: ReactionsDetailViewOptions) -> ReactionsDetailViewTypeDetail view showing who reacted
func makeBottomReactionsView(options: ReactionsBottomViewOptions) -> ReactionsBottomViewTypeReactions shown below a message
func makeMessageReactionView(options: MessageReactionViewOptions) -> MessageReactionViewTypeSingle reaction view
func makeReactionsOverlayView(options: ReactionsOverlayViewOptions) -> ReactionsOverlayViewTypeOverlay shown when adding a reaction
func makeReactionsBackgroundView(options: ReactionsBackgroundOptions) -> ReactionsBackgroundBackground view behind the reactions overlay
func makeReactionsContentView(options: ReactionsContentViewOptions) -> ReactionsContentViewContent view for the reactions picker
func makeMoreReactionsView(options: MoreReactionsViewOptions) -> MoreReactionsViewType"More reactions" button/view
func makeMessageReadIndicatorView(options: MessageReadIndicatorViewOptions) -> MessageReadIndicatorViewTypeRead indicator shown next to messages
func makeNewMessagesIndicatorView(options: NewMessagesIndicatorViewOptions) -> NewMessagesIndicatorViewTypeBanner indicating new messages arrived
func makeJumpToUnreadButton(options: JumpToUnreadButtonOptions) -> JumpToUnreadButtonTypeButton to jump to the first unread message
Polls
MethodDescription
func makeComposerPollView(options: ComposerPollViewOptions) -> ComposerPollViewTypePoll creation view in the composer
func makePollView(options: PollViewOptions) -> PollViewTypePoll attachment view in the message list
Thread List
MethodDescription
func makeThreadDestination(options: ThreadDestinationOptions) -> @MainActor (ChatThread) -> ThreadDestinationDestination view when navigating to a thread
func makeThreadListItem(options: ThreadListItemOptions<ThreadDestination>) -> ThreadListItemTypeView for each thread row in the thread list
func makeNoThreadsView(options: NoThreadsViewOptions) -> NoThreadsEmpty state view when there are no threads
func makeThreadListLoadingView(options: ThreadListLoadingViewOptions) -> ThreadListLoadingViewLoading state view for the thread list
func makeThreadListContainerViewModifier(options: ThreadListContainerModifierOptions) -> ThreadListContainerModifierModifier applied to the thread list container
func makeThreadListHeaderViewModifier(options: ThreadListHeaderViewModifierOptions) -> ThreadListHeaderViewModifierNavigation header modifier for the thread list
func makeThreadListHeaderView(options: ThreadListHeaderViewOptions) -> ThreadListHeaderViewHeader view shown at the top of the thread list
func makeThreadListFooterView(options: ThreadListFooterViewOptions) -> ThreadListFooterViewFooter shown when loading more threads
func makeThreadListBackground(options: ThreadListBackgroundOptions) -> ThreadListBackgroundBackground view for the thread list
func makeThreadListItemBackground(options: ThreadListItemBackgroundOptions) -> ThreadListItemBackgroundBackground view for each thread list item
func makeThreadListDividerItem(options: ThreadListDividerItemOptions) -> ThreadListDividerItemDivider between thread list items
Add Users And Text Rendering
MethodDescription
func makeAddUsersView(options: AddUsersViewOptions) -> AddUsersViewTypeAdd users view in the message composer
func makeAttachmentTextView(options: AttachmentTextViewOptions) -> AttachmentTextViewTypeText preview for attachments
func makeStreamTextView(options: StreamTextViewOptions) -> StreamTextViewTypeCustom 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

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 no channels view is NoChannels:

associatedtype NoChannels: View
func makeNoChannelsView(options: NoChannelsViewOptions) -> NoChannels

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

typealias NoChannels = CustomNoChannelsView

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

public func makeNoChannelsView(options: NoChannelsViewOptions) -> CustomNoChannelsView {
    CustomNoChannelsView()
}

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