Migrating from 4.x to 5.x

Version 5.x of the SwiftUI SDK keeps the overall architecture from 4.x, but it includes several source-breaking API updates in the customization layer. Most migrations are straightforward and fall into these buckets:

  • Appearance customization now goes through mutable properties instead of initializer parameters.
  • Several SwiftUI-specific helper views were renamed.
  • ViewFactory methods now take a single options object instead of many individual parameters.
  • Several modifier methods moved from ViewFactory to the new Styles protocol.
  • Utils gained new formatter parameters and dropped a few deprecated ones.
  • supportedMoreChannelActions, supportedMessageActions, and navigationBarDisplayMode moved to config objects.

If your app mostly uses the default UI components, the upgrade should be limited to dependency updates and a small number of customization fixes.


Please don't hesitate to contact our Support team or open a ticket in our GitHub repository. We'll help you during your migration process and any issues you might face.


Update the SDK Version

First, update your Swift Package Manager dependency to version 5.x.

Warning:

V5 no longer ships with CocoaPods support. If your project still uses CocoaPods for other dependencies, you can mix CocoaPods and Swift Package Manager in the same project — keep CocoaPods for your existing dependencies and add the Stream Chat SDK through SPM.

Appearance no longer uses parameterized initializers

In v4, many docs and examples initialized Appearance with parameters such as colors, fonts, or images.

In v5, Appearance has a public empty initializer and you customize it by mutating its properties:

let colors = Appearance.ColorPalette()
colors.accentPrimary = .systemPink
colors.navigationBarTintColor = .systemPink

let fonts = Appearance.FontsSwiftUI()
fonts.body = .system(size: 18)

let images = Appearance.Images()

let appearance = Appearance()
appearance.colorPalette = colors
appearance.fontsSwiftUI = fonts
appearance.images = images

let streamChat = StreamChat(chatClient: chatClient, appearance: appearance)

Replace usages like these:

let appearance = Appearance(colors: colors)
let appearance = Appearance(colors: colors, fonts: fonts, images: images)

with property-based configuration on a mutable Appearance value.

Use Appearance.FontsSwiftUI for SwiftUI font customization

Appearance now exposes both UIKit fonts and SwiftUI fonts:

  • appearance.fonts for UIKit font values
  • appearance.fontsSwiftUI for SwiftUI font values

If you are customizing SwiftUI screens, use Appearance.FontsSwiftUI:

let fonts = Appearance.FontsSwiftUI()
fonts.footnoteBold = .footnote

let appearance = Appearance()
appearance.fontsSwiftUI = fonts

If you were previously using Fonts() in SwiftUI snippets, switch those usages to Appearance.FontsSwiftUI().

Replace tintColor customizations with ColorPalette

The SwiftUI SDK reads brand and navigation colors from Appearance.colorPalette.

The most common v4 migration is replacing generic tint configuration with explicit palette values:

let colors = Appearance.ColorPalette()
colors.accentPrimary = .systemPink
colors.navigationBarTintColor = .systemPink

let appearance = Appearance()
appearance.colorPalette = colors

In custom SwiftUI views, when you need the configured brand color, prefer the palette values:

@Injected(\.colors) private var colors

Text("Create")
    .foregroundColor(Color(colors.accentPrimary))

Migrate removed ColorPalette tokens

The v5 color palette removed many broad aliases from v4 and now prefers semantic tokens such as textPrimary, backgroundCoreApp, chatBackgroundIncoming, and borderCoreDefault.

The table below lists suggested replacements. Some old tokens do not have a 1:1 replacement because the new palette uses more specific tokens for text, surfaces, chat bubbles, reactions, controls, and overlays.

Important: Before mapping every removed token individually, start by adjusting the brand and chrome ramps on Appearance.ColorPalette — most semantic tokens derive from them, so a small number of overrides reskins the whole SDK. Reach for the individual replacements below only when a specific surface needs to deviate from what the ramps produce.

v4 ColorPalette token v5 token(s) Notes
text textPrimary Use inputTextDefault, chatTextOutgoing, or chatTextTypingIndicator when customizing those specific components.
subtitleText textSecondary Use inputTextPlaceholder for input placeholders.
textLowEmphasis textTertiary Best match for low-emphasis labels and icons.
textInverted textOnInverse Use when text is shown on inverse backgrounds.
textLinkColor textLink or chatTextLink Use chatTextLink inside message content.
staticColorText textOnAccent Use chatTextSystem for date separators and system labels.
staticBlackColorText textPrimary Use semantic text tokens instead of static black text.
background backgroundCoreApp App and list backgrounds.
background1 backgroundCoreSurfaceSubtle Subtle surfaces and secondary containers.
background2 backgroundCoreSurfaceStrong Strong surfaces. Chat bubbles may need chatBackgroundOutgoing or chat border tokens instead.
background3 backgroundCoreOverlayDark Dark overlay backgrounds.
background4, background5 backgroundCoreScrim Scrims and dark media overlays.
background6 backgroundCoreSurfaceDefault Default surfaces. Message bubbles may need chatBackgroundIncoming or chatBackgroundOutgoing.
background7 accentNeutral or backgroundCoreSurfaceSubtle Use accentNeutral for neutral icon/control colors, and backgroundCoreSurfaceSubtle for surfaces.
background8 backgroundCoreElevation1 or chatBackgroundIncoming Use elevated surface tokens for popovers/cards, and chat tokens for message bubbles.
overlayBackground backgroundCoreOverlayLight Typing indicators now use chatBackgroundIncoming with alpha.
popoverBackground backgroundCoreElevation1 Reactions now use reactionBackground.
highlightedBackground backgroundCoreSurfaceStrong Highlighted rows, buttons, or selection surfaces.
highlightedAccentBackground accentPrimary Accent-highlighted UI now uses the primary accent token.
highlightedAccentBackground1 backgroundCoreSurfaceSubtle or backgroundCoreElevation1 Pick the surface token that matches the component hierarchy.
messageCellHighlightBackground, pinnedMessageBackground backgroundCoreHighlight Highlighted message and pinned-message backgrounds.
jumpToUnreadButtonBackground accentNeutral Used by neutral unread controls.
border borderCoreDefault Reactions now use reactionBorder.
border2 borderCoreStrong or controlCheckboxBorder Use a component token for controls when available.
border3 chatBorderIncoming, chatBorderOutgoing, or a core border token No general 1:1 replacement. Pick the token that matches the old usage.
lightBorder borderCoreSubtle Closest low-emphasis border replacement.
innerBorder borderCoreDefault Closest general replacement.
shadow, hoverButtonShadow backgroundCoreOverlayDark Used as the replacement shadow or overlay color in the UIKit migration.
alert, validationError accentError Error and destructive UI.
alternativeActiveTint accentSuccess Poll progress uses chatPollProgressFillIncoming.
inactiveTint accentNeutral or inputTextIcon Use inputTextIcon for composer/input icons.
alternativeInactiveTint backgroundUtilityDisabled or inputSendIconDisabled Choose the disabled token that matches the component.
disabledColorForColor, unselectedColorForColor textDisabled, backgroundUtilityDisabled, or component-specific disabled tokens No direct replacement. Prefer semantic disabled tokens.
highlightedColorForColor highlightedColorForColor Still available.
navigationBarTitle navigationBarTitle Still available, but now defaults to textPrimary.
navigationBarSubtitle navigationBarSubtitle Still available, but now defaults to textTertiary.
navigationBarBackground navigationBarBackground Still available.
navigationBarTintColor navigationBarTintColor Still available, defaulting to accentPrimary.
navigationBarGlyph navigationBarGlyph Still available, now defaulting to .baseWhite.
reactionCurrentUserColor, reactionOtherUserColor reactionText, reactionBackground, reactionBorder, or accentPrimary Pick based on whether you customized reaction text, selected state, background, or border.
selectedReactionBackgroundColor reactionBackground, reactionBorder, or accentPrimary No exact replacement. Choose the reaction token that matches the customized selected state.
quotedMessageBackgroundCurrentUser, quotedMessageBackgroundOtherUser backgroundCoreElevation1, backgroundCoreSurfaceSubtle, or chat attachment/message tokens Pick based on whether the quoted content behaves like an elevated surface, subtle surface, attachment, or message bubble.
composerInputHighlightedBorder borderCoreDefault Closest general composer border replacement.
voiceMessageControlBackground controlPlaybackThumbBackgroundDefault, controlPlayButtonBackground, or backgroundCoreOnAccent Use the control-specific token that matches the customized voice-message control.
messageCurrentUserBackground chatBackgroundOutgoing Current-user message bubble background.
messageCurrentUserEmphemeralBackground chatBackgroundOutgoing, chatBackgroundIncoming, or backgroundCoreElevation1 No exact replacement. Pick the token that matches the old ephemeral-message visual role.
messageOtherUserBackground chatBackgroundIncoming Other-user message bubble background.
messageCurrentUserTextColor chatTextOutgoing Current-user message text.
messageOtherUserTextColor chatTextIncoming Other-user message text.
bannerBackgroundColor textTertiary or accentNeutral No exact replacement. Pick based on banner style.
composerInputBackground backgroundCoreApp or a surface token Choose the app background or surface token that matches your composer design.
composerPlaceholderColor inputTextPlaceholder Composer placeholder text.
messageLinkAttachmentAuthorColor chatTextLink or textLink Use chatTextLink inside message/link attachment content.
messageLinkAttachmentTitleColor textPrimary Link attachment title text.
messageLinkAttachmentTextColor textPrimary or textSecondary Pick based on text hierarchy.

Gradient message bubbles

In v4, SwiftUI message bubble colors were arrays, so you could create gradients directly on ColorPalette:

colors.messageCurrentUserBackground = [purple, pink]
colors.messageCurrentUserEmphemeralBackground = [
    purple.withAlphaComponent(0.85),
    pink.withAlphaComponent(0.85)
]
colors.messageOtherUserBackground = [card]

In v5, the main chat background tokens such as chatBackgroundOutgoing and chatBackgroundIncoming are single UIColor values. Migrating the code above directly to colors.chatBackgroundOutgoing = purple keeps the app compiling, but it also changes the outgoing bubble from a gradient to a solid color.

Single-color bubbles can keep using the default MessageBubbleModifier. For gradient bubbles, move the customization to Styles.makeMessageViewModifier(for:) and use the lower-level BubbleModifier, which accepts backgroundColors: [Color] and renders a LinearGradient when more than one color is provided:

RegularStyles is public class (not open), so you cannot subclass it from your own module. Conform to Styles directly and provide the three required methods that have no protocol-extension default (makeComposerInputViewModifier, makeComposerButtonViewModifier, makeSuggestionsContainerModifier) using the public Regular* modifier types:

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

    func makeMessageViewModifier(for info: MessageModifierInfo) -> some ViewModifier {
        GradientMessageBubbleModifier(info: info)
    }

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

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

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

private struct GradientMessageBubbleModifier: ViewModifier {
    @Environment(\.layoutDirection) private var layoutDirection

    let info: MessageModifierInfo

    func body(content: Content) -> some View {
        let backgroundColors = info.injectedBackgroundColor.map { [Color($0)] } ?? [.purple, .pink]

        content.modifier(
            BubbleModifier(
                corners: info.message.bubbleCorners(
                    isFirst: info.isFirst,
                    forceLeftToRight: info.forceLeftToRight,
                    layoutDirection: layoutDirection
                ),
                backgroundColors: backgroundColors,
                cornerRadius: info.cornerRadius ?? 18
            )
        )
    }
}

DefaultViewFactory.shared.styles = CustomStyles()

channelNamer became channelNameFormatter

Channel name customization changed in v5.

Replace:

let channelNamer: ChatChannelNamer = { channel, currentUserId in
    "This is our custom name: \(channel.name ?? "no name")"
}

let utils = Utils(channelNamer: channelNamer)

with:

final class CustomChannelNameFormatter: ChannelNameFormatter {
    func format(channel: ChatChannel, forCurrentUserId currentUserId: UserId?) -> String? {
        "This is our custom name: \(channel.name ?? "no name")"
    }
}

let utils = Utils(channelNameFormatter: CustomChannelNameFormatter())

If you were using DefaultChatChannelNamer, migrate to DefaultChannelNameFormatter.

Update renamed reusable SwiftUI views

If you built custom UI with the SDK's reusable views, update the renamed view types:

  • MessageAvatarView -> UserAvatar
  • ChannelAvatarView -> ChannelAvatar
  • TypingIndicatorBottomView -> TypingIndicatorView

For example, this v4 code:

MessageAvatarView(
    url: user.imageURL,
    size: 40
)

becomes:

UserAvatar(
    user: user,
    size: 40
)

Note that UserAvatar takes a ChatUser value directly rather than a URL, and size is now a CGFloat instead of CGSize. If you only have a URL (for example in a quoted message context), use the URL-and-initials initializer:

UserAvatar(
    url: url,
    initials: initials,
    size: 40,
    indicator: .none
)

The showOnlineIndicator: Bool parameter was replaced by an AvatarIndicator enum: .online, .offline, or .none.

And this:

TypingIndicatorBottomView(
    users: users,
    typingIndicatorString: typingText
)

becomes:

TypingIndicatorView(
    users: users,
    typingText: typingText
)

MessageDisplayOptions avatar parameters split

In v4, MessageDisplayOptions had a single showAvatars: Bool parameter that controlled both incoming and outgoing message avatars. In v5, this is split into two parameters with different defaults:

v4 parameter v5 parameter(s)
showAvatars: Bool showIncomingMessageAvatar: Bool (default true), showOutgoingMessageAvatar: Bool (default false)

Replace:

let displayOptions = MessageDisplayOptions(
    showAvatars: false,
    showAvatarsInGroups: false
)

with:

let displayOptions = MessageDisplayOptions(
    showIncomingMessageAvatar: false,
    showOutgoingMessageAvatar: false,
    showAvatarsInGroups: false
)

The default behavior also changed — v5 shows incoming avatars and hides outgoing avatars unless you specify otherwise. If you previously relied on MessageDisplayOptions() defaults, your outgoing-message avatar visibility flips.

Utils API changes

Several parameters were added, removed, or renamed on the Utils initializer. The Utils class itself is now marked @MainActor.

Removed parameters:

v4 parameter Notes
imageMerger Removed; avatar merging is handled internally.
channelAvatarsMerger Removed; avatar merging is handled internally.
channelHeaderLoader Removed.
channelNamer Renamed — see channelNameFormatter below.

Renamed parameter:

v4 v5
channelNamer: ChatChannelNamer (closure) channelNameFormatter: any ChannelNameFormatter (protocol)

New parameters in v5:

Parameter Purpose
markdownFormatter Provides custom Markdown rendering for message text.
messageTimestampFormatter Formats the timestamp shown on individual messages.
galleryHeaderViewDateFormatter Formats the date in the media gallery header.
messageDateSeparatorFormatter Formats the date separator labels in the message list.
mediaLoader Unified loader for images and video previews (replaces imageLoader + videoLoader).
messageAttachmentPreviewIconProvider Provides icons for attachment preview thumbnails.
messagePreviewFormatter Formats the message preview text shown in the channel list.
channelListConfig Configuration object for the channel list (see below).
mediaBadgeDurationFormatter Formats duration badges on audio and video attachments.
messageRemindersFormatter Formats message reminder labels (conforms to MessageRemindersFormatter).

CDN and Media Loading

v5 replaces several image/video loading and CDN types with a unified architecture. If you customized any of these in v4, update to the new types.

Image and Video Loading

The separate imageLoader and videoLoader on Utils are replaced by a single mediaLoader:

// v4
let utils = Utils(
    imageLoader: CustomImageLoader(),
    videoLoader: CustomVideoLoader()
)

// v5
let utils = Utils(
    mediaLoader: CustomMediaLoader()
)

The underlying protocols changed as follows:

v4 Type v5 Replacement
ImageLoading / NukeImageLoader MediaLoader / StreamMediaLoader
VideoLoading / VideoPreviewLoader MediaLoader (via loadVideoPreview)
StreamVideoLoader / DefaultVideoPreviewLoader StreamMediaLoader

CDN Customization

CDN URL handling was split into two distinct concerns: storage (uploads/deletes) and requesting (signed URLs, auth headers, resizing).

v4 Type v5 Replacement
CDNClient CDNStorage
AttachmentUploader CDNStorage + UploadedAttachmentPostProcessor
ChatClientConfig.customCDNClient ChatClientConfig.cdnStorage
ChatClientConfig.customAttachmentUploader ChatClientConfig.cdnStorage
ImageCDN / StreamImageCDN CDNRequester / StreamCDNRequester
FileCDN CDNRequester (via fileRequest)

Configure CDNStorage on ChatClientConfig:

var config = ChatClientConfig(apiKeyString: apiKeyString)
config.cdnStorage = CustomStorage()

Configure CDNRequester through StreamMediaLoader:

// UIKit
Components.default.mediaLoader = StreamMediaLoader(downloader: StreamImageDownloader(), cdnRequester: CustomCDNRequester())

// SwiftUI
let loader = StreamMediaLoader(downloader: StreamImageDownloader(), cdnRequester: CustomCDNRequester())
let utils = Utils(mediaLoader: loader)
Tip:

For detailed documentation and examples of CDNStorage, CDNRequester, and MediaLoader, see the Custom CDN guide.

In v4 these were methods on the ViewFactory protocol. In v5 they are properties on the relevant config objects.

DefaultViewFactory still exposes a compatibility navigationBarDisplayMode() helper on the v5 branch, but the channel list and message list read ChannelListConfig.navigationBarDisplayMode and MessageListConfig.navigationBarDisplayMode. For migrations, move your customization to the config objects.

navigationBarDisplayMode — remove the ViewFactory override and set the property on ChannelListConfig or MessageListConfig instead:

// v4 – ViewFactory override
func navigationBarDisplayMode() -> NavigationBarItem.TitleDisplayMode { .inline }

// v5 – config property
var channelListConfig = ChannelListConfig()
channelListConfig.navigationBarDisplayMode = .inline

var messageListConfig = MessageListConfig()
messageListConfig.navigationBarDisplayMode = .inline

let utils = Utils(channelListConfig: channelListConfig, messageListConfig: messageListConfig)

supportedMoreChannelActions — remove the ViewFactory override and supply a closure on ChannelListConfig:

// v4 – ViewFactory override
func supportedMoreChannelActions(
    for channel: ChatChannel,
    onDismiss: @escaping () -> Void,
    onError: @escaping (Error) -> Void
) -> [ChannelAction] { ... }

// v5 – ChannelListConfig closure
var channelListConfig = ChannelListConfig()
channelListConfig.supportedMoreChannelActions = { options in
    // options.channel, options.onDismiss, options.onError
    return [...]
}

supportedMessageActions — remove the ViewFactory override and supply a closure on MessageListConfig:

// v4 – ViewFactory override
func supportedMessageActions(
    for message: ChatMessage,
    channel: ChatChannel,
    onFinish: @escaping (MessageActionInfo) -> Void,
    onError: @escaping (Error) -> Void
) -> [MessageAction] { ... }

// v5 – MessageListConfig closure
var messageListConfig = MessageListConfig()
messageListConfig.supportedMessageActions = { options in
    // options.message, options.channel, options.onFinish, options.onError
    return [...]
}

New Styles protocol — modifier customization moved out of ViewFactory

v5 introduces a Styles protocol that owns all ViewModifier-returning customizations. Several methods that were on ViewFactory in v4 now live on Styles instead.

The SDK ships two concrete implementations:

  • LiquidGlassStyles — uses the iOS 26 Liquid Glass material where available.
  • RegularStyles — the classic appearance for earlier OS versions.

DefaultViewFactory uses RegularStyles by default. If you want the built-in Liquid Glass look, set styles = LiquidGlassStyles() on your custom factory.

RegularStyles and LiquidGlassStyles are both declared public class (not open), so you cannot subclass them from your own module. To customize modifier behavior, conform to Styles directly. The protocol provides default implementations for most methods, but three methods have no default and must be implemented: makeComposerInputViewModifier(options:), makeComposerButtonViewModifier(options:), and makeSuggestionsContainerModifier(options:). The public RegularInputViewModifier, RegularButtonViewModifier, and SuggestionsRegularContainerModifier types match the default appearance and can be reused:

final class MyStyles: Styles {
    var composerPlacement: ComposerPlacement = .docked

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

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

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

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

final class CustomFactory: ViewFactory {
    @Injected(\.chatClient) public var chatClient
    public var styles = MyStyles()

    private init() {}
    public static let shared = CustomFactory()
}

For channel list search, this change is behavioral as well as structural. In v5, search is applied through Styles.makeSearchableModifier(options:). makeChannelListTopView(options:) remains available, but it is now just a top content slot and no longer provides searchText or owns the search bar UI.

Methods moved from ViewFactory to Styles:

v4 ViewFactory method v5 Styles method
func makeChannelListContentModifier() -> ChannelListContentModifier func makeChannelListContentModifier(options: ChannelListContentModifierOptions) -> ChannelListContentModifier
func makeChannelListModifier() -> ChannelListModifier func makeChannelListModifier(options: ChannelListModifierOptions) -> ChannelListModifier
func makeMessageListModifier() -> MessageListModifier func makeMessageListModifier(options: MessageListModifierOptions) -> MessageListModifier
func makeMessageListContainerModifier() -> MessageListContainerModifier func makeMessageListContainerModifier(options: MessageListContainerModifierOptions) -> MessageListContainerModifier
func makeMessageViewModifier(for messageModifierInfo: MessageModifierInfo) -> MessageViewModifier func makeMessageViewModifier(for messageModifierInfo: MessageModifierInfo) -> MessageViewModifier
func makeComposerViewModifier() -> ComposerViewModifier func makeComposerViewModifier(options: ComposerViewModifierOptions) -> ComposerViewModifier

New Styles methods with no v4 equivalent:

  • func makeComposerInputViewModifier(options: ComposerInputModifierOptions) -> ComposerInputViewModifier
  • func makeComposerButtonViewModifier(options: ComposerButtonModifierOptions) -> ComposerButtonViewModifier
  • func makeScrollToBottomButtonModifier(options: ScrollToBottomButtonModifierOptions) -> ScrollToBottomButtonViewModifier
  • func makeBouncedMessageActionsModifier(viewModel: ChatChannelViewModel) -> BouncedMessageActionsModifierType
  • func makeSuggestionsContainerModifier(options: SuggestionsContainerModifierOptions) -> SuggestionsContainerModifier
  • func makeToolbarConfirmActionModifier(options: ToolbarConfirmActionModifierOptions) -> ToolbarConfirmActionViewModifier
  • func makeSearchableModifier(options: SearchableModifierOptions) -> SearchableModifierType
  • func makeMessageAttachmentsViewModifier(options: MessageAttachmentsViewModifierOptions) -> MessageAttachmentsViewModifier
  • func makeMessageAttachmentItemViewModifier(options: MessageAttachmentItemViewModifierOptions) -> MessageAttachmentItemViewModifier

Translated text rendering changes (LinkDetectionTextView replacement)

Text rendering now goes through StreamTextView, replacing the earlier LinkDetectionTextView path.

For custom text rendering, use ChatMessage.attributedTextContent(layoutDirection:translationLanguage:) to build the attributed content shown in Text(...).

If you have custom text views or custom ViewFactory overrides, pass translationLanguage explicitly through options objects. These types now carry that value:

  • StreamTextViewOptions(message:translationLanguage:)
  • AttachmentTextViewOptions(message:availableWidth:translationLanguage:)
  • MessageTextViewOptions(..., translationLanguage:)
  • MessageAttachmentsViewOptions(..., translationLanguage:)
// v4
public func makeStreamTextView(options: StreamTextViewOptions) -> some View {
    StreamTextView(message: options.message)
}

// v5
public func makeStreamTextView(options: StreamTextViewOptions) -> some View {
    MyStreamTextView(
        message: options.message,
        translationLanguage: options.translationLanguage
    )
}

struct MyStreamTextView: View {
    @Environment(\.layoutDirection) private var layoutDirection

    let message: ChatMessage
    let translationLanguage: TranslationLanguage?

    var body: some View {
        Text(
            message.attributedTextContent(
                layoutDirection: layoutDirection,
                translationLanguage: translationLanguage
            )
        )
    }
}

Review your ViewFactory implementations

The biggest ViewFactory change in v5 is that the API moved from many ad-hoc parameters to option objects. If you had a custom factory in v4, you should review every override.

Below is the full mapping validated against the v4 and v5 of stream-chat-swiftui.

Channel List
v4 v5
func makeChannelAvatarView(for channel: ChatChannel, with options: ChannelAvatarViewOptions) -> ChannelAvatarViewType func makeChannelAvatarView(options: ChannelAvatarViewOptions) -> ChannelAvatarViewType
func makeChannelListBackground(colors: ColorPalette) -> ChannelListBackground func makeChannelListBackground(options: ChannelListBackgroundOptions) -> ChannelListBackground
func makeChannelListContentModifier() -> ChannelListContentModifier moved to Styles, see the Styles section above
func makeChannelListDividerItem() -> ChannelListDividerItem func makeChannelListDividerItem(options: ChannelListDividerItemOptions) -> ChannelListDividerItem
func makeChannelListFooterView() -> ChannelListFooterViewType func makeChannelListFooterView(options: ChannelListFooterViewOptions) -> ChannelListFooterViewType
func makeChannelListHeaderViewModifier(title: String) -> HeaderViewModifier func makeChannelListHeaderViewModifier(options: ChannelListHeaderViewModifierOptions) -> HeaderViewModifier
func makeChannelListItem(channel: ChatChannel, channelName: String, avatar: UIImage, onlineIndicatorShown: Bool, disabled: Bool, selectedChannel: Binding<ChannelSelectionInfo?>, swipedChannelId: Binding<String?>, channelDestination: @escaping (ChannelSelectionInfo) -> ChannelDestination, onItemTap: @escaping (ChatChannel) -> Void, trailingSwipeRightButtonTapped: @escaping (ChatChannel) -> Void, trailingSwipeLeftButtonTapped: @escaping (ChatChannel) -> Void, leadingSwipeButtonTapped: @escaping (ChatChannel) -> Void) -> ChannelListItemType func makeChannelListItem(options: ChannelListItemOptions<ChannelDestination>) -> ChannelListItemType
func makeChannelListItemBackground(channel: ChatChannel, isSelected: Bool) -> ChannelListItemBackground func makeChannelListItemBackground(options: ChannelListItemBackgroundOptions) -> ChannelListItemBackground
func makeChannelListModifier() -> ChannelListModifier moved to Styles, see the Styles section above
func makeChannelListSearchResultItem(searchResult: ChannelSelectionInfo, onlineIndicatorShown: Bool, channelName: String, avatar: UIImage, onSearchResultTap: @escaping (ChannelSelectionInfo) -> Void, channelDestination: @escaping (ChannelSelectionInfo) -> ChannelDestination) -> ChannelListSearchResultItem func makeChannelListSearchResultItem(options: ChannelListSearchResultItemOptions<ChannelDestination>) -> ChannelListSearchResultItem
func makeChannelListStickyFooterView() -> ChannelListStickyFooterViewType func makeChannelListStickyFooterView(options: ChannelListStickyFooterViewOptions) -> ChannelListStickyFooterViewType
func makeChannelListTopView(searchText: Binding<String>) -> ChannelListTopViewType func makeChannelListTopView(options: ChannelListTopViewOptions) -> ChannelListTopViewType
func makeLeadingSwipeActionsView(channel: ChatChannel, offsetX: CGFloat, buttonWidth: CGFloat, swipedChannelId: Binding<String?>, buttonTapped: @escaping (ChatChannel) -> Void) -> LeadingSwipeActionsViewType func makeLeadingSwipeActionsView(options: LeadingSwipeActionsViewOptions) -> LeadingSwipeActionsViewType
func makeLoadingView() -> LoadingContent func makeLoadingView(options: LoadingViewOptions) -> LoadingContent
func makeMoreChannelActionsView(for channel: ChatChannel, swipedChannelId: Binding<String?>, onDismiss: @escaping () -> Void, onError: @escaping (Error) -> Void) -> MoreActionsView func makeMoreChannelActionsView(options: MoreChannelActionsViewOptions) -> MoreActionsView
func makeNoChannelsView() -> NoChannels func makeEmptyChannelsView(options: EmptyChannelsViewOptions) -> EmptyChannels
func makeSearchResultsView(selectedChannel: Binding<ChannelSelectionInfo?>, searchResults: [ChannelSelectionInfo], loadingSearchResults: Bool, onlineIndicatorShown: @escaping (ChatChannel) -> Bool, channelNaming: @escaping (ChatChannel) -> String, imageLoader: @escaping (ChatChannel) -> UIImage, onSearchResultTap: @escaping (ChannelSelectionInfo) -> Void, onItemAppear: @escaping (Int) -> Void) -> ChannelListSearchResultsViewType func makeSearchResultsView(options: SearchResultsViewOptions) -> ChannelListSearchResultsViewType
func makeTrailingSwipeActionsView(channel: ChatChannel, offsetX: CGFloat, buttonWidth: CGFloat, swipedChannelId: Binding<String?>, leftButtonTapped: @escaping (ChatChannel) -> Void, rightButtonTapped: @escaping (ChatChannel) -> Void) -> TrailingSwipeActionsViewType func makeTrailingSwipeActionsView(options: TrailingSwipeActionsViewOptions) -> TrailingSwipeActionsViewType
func supportedMoreChannelActions(for channel: ChatChannel, onDismiss: @escaping () -> Void, onError: @escaping (Error) -> Void) -> [ChannelAction] moved to Utils.channelListConfig.supportedMoreChannelActions closure
Channel And Message List
v4 v5
func makeAssetsAccessPermissionView() -> AssetsAccessPermissionViewType removed in v5, no direct replacement
func makeAttachmentPickerView(attachmentPickerState: Binding<AttachmentPickerState>, filePickerShown: Binding<Bool>, cameraPickerShown: Binding<Bool>, addedFileURLs: Binding<[URL]>, onPickerStateChange: @escaping (AttachmentPickerState) -> Void, photoLibraryAssets: PHFetchResult<PHAsset>?, onAssetTap: @escaping (AddedAsset) -> Void, onCustomAttachmentTap: @escaping (CustomAttachment) -> Void, isAssetSelected: @escaping (String) -> Bool, addedCustomAttachments: [CustomAttachment], cameraImageAdded: @escaping (AddedAsset) -> Void, askForAssetsAccessPermissions: @escaping () -> Void, isDisplayed: Bool, height: CGFloat, popupHeight: CGFloat) -> AttachmentPickerViewType func makeAttachmentPickerView(options: AttachmentPickerViewOptions) -> AttachmentPickerViewType
func makeAttachmentSourcePickerView(selected: AttachmentPickerState, onPickerStateChange: @escaping (AttachmentPickerState) -> Void) -> AttachmentSourcePickerViewType func makeAttachmentTypePickerView(options: AttachmentTypePickerViewOptions) -> AttachmentTypePickerViewType
func makeBottomReactionsView(message: ChatMessage, showsAllInfo: Bool, onTap: @escaping () -> Void, onLongPress: @escaping () -> Void) -> ReactionsBottomViewType func makeBottomReactionsView(options: ReactionsBottomViewOptions) -> ReactionsBottomViewType
func makeBouncedMessageActionsModifier(viewModel: ChatChannelViewModel) -> BouncedMessageActionsModifierType moved to Styles, see the Styles section above
func makeCameraPickerView(selected: Binding<AttachmentPickerState>, cameraPickerShown: Binding<Bool>, cameraImageAdded: @escaping (AddedAsset) -> Void) -> CameraPickerViewType func makeAttachmentCameraPickerView(options: AttachmentCameraPickerViewOptions) -> AttachmentCameraPickerViewType
func makeChannelBarsVisibilityViewModifier(shouldShow: Bool) -> ChangeBarsVisibilityModifier func makeChannelBarsVisibilityViewModifier(options: ChannelBarsVisibilityViewModifierOptions) -> ChangeBarsVisibilityModifier
func makeChannelDestination() -> (ChannelSelectionInfo) -> ChannelDestination func makeChannelDestination(options: ChannelDestinationOptions) -> @MainActor (ChannelSelectionInfo) -> ChannelDestination
func makeChannelHeaderViewModifier(for channel: ChatChannel) -> ChatHeaderViewModifier func makeChannelHeaderViewModifier(options: ChannelHeaderViewModifierOptions) -> ChatHeaderViewModifier
func makeChannelLoadingView() -> ChannelLoadingViewType func makeChannelLoadingView(options: ChannelLoadingViewOptions) -> ChannelLoadingViewType
func makeCommandsContainerView(suggestions: [String: Any], handleCommand: @escaping ([String: Any]) -> Void) -> CommandsContainerViewType func makeSuggestionsContainerView(options: SuggestionsContainerViewOptions) -> SuggestionsContainerViewType
func makeComposerInputView(text: Binding<String>, selectedRangeLocation: Binding<Int>, command: Binding<ComposerCommand?>, addedAssets: [AddedAsset], addedFileURLs: [URL], addedCustomAttachments: [CustomAttachment], quotedMessage: Binding<ChatMessage?>, maxMessageLength: Int?, cooldownDuration: Int, onCustomAttachmentTap: @escaping (CustomAttachment) -> Void, shouldScroll: Bool, removeAttachmentWithId: @escaping (String) -> Void) -> ComposerInputViewType func makeComposerInputView(options: ComposerInputViewOptions) -> ComposerInputViewType
func makeComposerRecordingLockedView(viewModel: MessageComposerViewModel) -> ComposerRecordingLockedViewType func makeComposerVoiceRecordingInputView(options: ComposerVoiceRecordingInputViewOptions) -> ComposerVoiceRecordingInputViewType
func makeComposerRecordingTipView() -> ComposerRecordingTipViewType func makeComposerVoiceRecordingInputView(options: ComposerVoiceRecordingInputViewOptions) -> ComposerVoiceRecordingInputViewType
func makeComposerRecordingView(viewModel: MessageComposerViewModel, gestureLocation: CGPoint) -> ComposerRecordingViewType func makeComposerVoiceRecordingInputView(options: ComposerVoiceRecordingInputViewOptions) -> ComposerVoiceRecordingInputViewType
func makeComposerTextInputView(text: Binding<String>, height: Binding<CGFloat>, selectedRangeLocation: Binding<Int>, placeholder: String, editable: Bool, maxMessageLength: Int?, currentHeight: CGFloat) -> ComposerTextInputViewType func makeComposerTextInputView(options: ComposerTextInputViewOptions) -> ComposerTextInputViewType
func makeComposerViewModifier() -> ComposerViewModifier moved to Styles, see the Styles section above
func makeCustomAttachmentPreviewView(addedCustomAttachments: [CustomAttachment], onCustomAttachmentTap: @escaping (CustomAttachment) -> Void) -> CustomAttachmentPreviewViewType func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions) -> CustomAttachmentPreviewViewType
func makeCustomAttachmentQuotedView(for message: ChatMessage) -> CustomAttachmentQuotedViewType removed in v5, no direct replacement
func makeCustomAttachmentView(addedCustomAttachments: [CustomAttachment], onCustomAttachmentTap: @escaping (CustomAttachment) -> Void) -> CustomComposerAttachmentViewType func makeCustomAttachmentPickerView(options: CustomAttachmentPickerViewOptions) -> CustomAttachmentPickerViewType
func makeCustomAttachmentViewType(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat, scrolledId: Binding<String?>) -> CustomAttachmentViewType func makeCustomAttachmentViewType(options: CustomAttachmentViewTypeOptions) -> CustomAttachmentViewType
func makeDateIndicatorView(dateString: String) -> DateIndicatorViewType func makeDateIndicatorView(options: DateIndicatorViewOptions) -> DateIndicatorViewType
func makeDeletedMessageView(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat) -> DeletedMessageViewType func makeDeletedMessageView(options: DeletedMessageViewOptions) -> DeletedMessageViewType
func makeEditedMessageHeaderView(editedMessage: Binding<ChatMessage?>) -> EditedMessageHeaderViewType func makeComposerEditedMessageView(options: ComposerEditedMessageViewOptions) -> ComposerEditedMessageViewType
func makeEmojiTextView(message: ChatMessage, scrolledId: Binding<String?>, isFirst: Bool) -> EmojiTextViewType func makeEmojiTextView(options: EmojiTextViewOptions) -> EmojiTextViewType
func makeEmptyMessagesView(for channel: ChatChannel, colors: ColorPalette) -> EmptyMessagesViewType func makeEmptyMessagesView(options: EmptyMessagesViewOptions) -> EmptyMessagesViewType
func makeFileAttachmentView(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat, scrolledId: Binding<String?>) -> FileAttachmentViewType func makeFileAttachmentView(options: FileAttachmentViewOptions) -> FileAttachmentViewType
func makeFilePickerView(filePickerShown: Binding<Bool>, addedFileURLs: Binding<[URL]>) -> FilePickerViewType func makeAttachmentFilePickerView(options: AttachmentFilePickerViewOptions) -> AttachmentFilePickerViewType
func makeGalleryHeaderView(title: String, subtitle: String, shown: Binding<Bool>) -> GalleryHeaderViewType func makeMediaViewerToolbarModifier(options: MediaViewerToolbarModifierOptions) -> MediaViewerToolbarModifierType
func makeGalleryHeaderView(title: String, subtitle: String, shown: Binding<Bool>) -> GalleryHeaderViewType func makeMediaViewerFooterView(options: MediaViewerFooterViewOptions) -> MediaViewerFooterViewType
func makeGalleryView(mediaAttachments: [MediaAttachment], message: ChatMessage, isShown: Binding<Bool>, options: MediaViewsOptions) -> GalleryViewType func makeMediaViewer(options: MediaViewerOptions) -> MediaViewerType
func makeGiphyAttachmentView(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat, scrolledId: Binding<String?>) -> GiphyAttachmentViewType func makeGiphyAttachmentView(options: GiphyAttachmentViewOptions) -> GiphyAttachmentViewType
func makeGiphyBadgeViewType(for message: ChatMessage, availableWidth: CGFloat) -> GiphyBadgeViewType func makeGiphyBadgeViewType(options: GiphyBadgeViewTypeOptions) -> GiphyBadgeViewType
func makeImageAttachmentView(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat, scrolledId: Binding<String?>) -> ImageAttachmentViewType func makeImageAttachmentView(options: ImageAttachmentViewOptions) -> ImageAttachmentViewType
func makeJumpToUnreadButton(channel: ChatChannel, onJumpToMessage: @escaping () -> Void, onClose: @escaping () -> Void) -> JumpToUnreadButtonType func makeJumpToUnreadButtonOverlay(options: JumpToUnreadButtonOptions) -> JumpToUnreadButtonOverlayType
func makeLastInGroupHeaderView(for message: ChatMessage) -> LastInGroupHeaderView func makeLastInGroupHeaderView(options: LastInGroupHeaderViewOptions) -> LastInGroupHeaderView
func makeLeadingComposerView(state: Binding<PickerTypeState>, channelConfig: ChannelConfig?) -> LeadingComposerViewType func makeLeadingComposerView(options: LeadingComposerViewOptions) -> LeadingComposerViewType
func makeLinkAttachmentView(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat, scrolledId: Binding<String?>) -> LinkAttachmentViewType func makeLinkAttachmentView(options: LinkAttachmentViewOptions) -> LinkAttachmentViewType
func makeMessageActionsView(for message: ChatMessage, channel: ChatChannel, onFinish: @escaping (MessageActionInfo) -> Void, onError: @escaping (Error) -> Void) -> MessageActionsViewType func makeMessageActionsView(options: MessageActionsViewOptions) -> MessageActionsViewType
func makeMessageAuthorAndDateView(for message: ChatMessage) -> MessageAuthorAndDateViewType func makeMessageAuthorAndDateView(options: MessageAuthorAndDateViewOptions) -> MessageAuthorAndDateViewType
func makeMessageAvatarView(for userDisplayInfo: UserDisplayInfo) -> UserAvatar func makeUserAvatarView(options: UserAvatarViewOptions) -> UserAvatarViewType
func makeMessageComposerViewType(with channelController: ChatChannelController, messageController: ChatMessageController?, quotedMessage: Binding<ChatMessage?>, editedMessage: Binding<ChatMessage?>, onMessageSent: @escaping () -> Void) -> MessageComposerViewType func makeMessageComposerViewType(options: MessageComposerViewTypeOptions) -> MessageComposerViewType
func makeMessageContainerView(channel: ChatChannel, message: ChatMessage, width: CGFloat?, showsAllInfo: Bool, isInThread: Bool, scrolledId: Binding<String?>, quotedMessage: Binding<ChatMessage?>, onLongPress: @escaping (MessageDisplayInfo) -> Void, isLast: Bool) -> MessageContainerViewType func makeMessageItemView(options: MessageItemViewOptions) -> MessageItemViewType
func makeMessageDateView(for message: ChatMessage) -> MessageDateViewType func makeMessageDateView(options: MessageDateViewOptions) -> MessageDateViewType
func makeMessageListBackground(colors: ColorPalette, isInThread: Bool) -> MessageListBackground func makeMessageListBackground(options: MessageListBackgroundOptions) -> MessageListBackground
func makeMessageListContainerModifier() -> MessageListContainerModifier moved to Styles, see the Styles section above
func makeMessageListDateIndicator(date: Date) -> MessageListDateIndicatorViewType func makeMessageListDateIndicator(options: MessageListDateIndicatorViewOptions) -> MessageListDateIndicatorViewType
func makeMessageListModifier() -> MessageListModifier moved to Styles, see the Styles section above
func makeMessageReactionView(message: ChatMessage, onTapGesture: @escaping () -> Void, onLongPressGesture: @escaping () -> Void) -> MessageReactionViewType func makeMessageReactionView(options: MessageReactionViewOptions) -> MessageReactionViewType
func makeMessageReadIndicatorView(channel: ChatChannel, message: ChatMessage) -> MessageReadIndicatorViewType func makeMessageReadIndicatorView(options: MessageReadIndicatorViewOptions) -> MessageReadIndicatorViewType
func makeMessageRepliesShownInChannelView(channel: ChatChannel, message: ChatMessage, parentMessage: ChatMessage, replyCount: Int) -> MessageRepliesShownInChannelViewType removed in v5, compose reply/thread annotations with func makeMessageTopView(options: MessageTopViewOptions) -> MessageTopViewType and keep reply-count customization in func makeMessageRepliesView(options: MessageRepliesViewOptions) -> MessageRepliesViewType
func makeMessageRepliesView(channel: ChatChannel, message: ChatMessage, replyCount: Int) -> MessageRepliesViewType func makeMessageRepliesView(options: MessageRepliesViewOptions) -> MessageRepliesViewType
func makeMessageTextView(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat, scrolledId: Binding<String?>) -> MessageTextViewType func makeMessageTextView(options: MessageTextViewOptions) -> MessageTextViewType
func makeMessageThreadDestination() -> (ChatChannel, ChatMessage) -> MessageThreadDestination func makeMessageThreadDestination(options: MessageThreadDestinationOptions) -> @MainActor (ChatChannel, ChatMessage) -> MessageThreadDestination
func makeMessageThreadHeaderViewModifier() -> ThreadHeaderViewModifier func makeMessageThreadHeaderViewModifier(options: MessageThreadHeaderViewModifierOptions) -> ThreadHeaderViewModifier
func makeMessageTranslationFooterView(messageViewModel: MessageViewModel) -> MessageTranslationFooterViewType removed in v5, translated/pinned/reply annotations are now composed by func makeMessageTopView(options: MessageTopViewOptions) -> MessageTopViewType
func makeMessageViewModifier(for messageModifierInfo: MessageModifierInfo) -> MessageViewModifier moved to Styles, see the Styles section above
func makeNewMessagesIndicatorView(newMessagesStartId: Binding<String?>, count: Int) -> NewMessagesIndicatorViewType func makeNewMessagesDividerView(options: NewMessagesDividerViewOptions) -> NewMessagesDividerType
func makePhotoAttachmentPickerView(assets: PHFetchResultCollection, onAssetTap: @escaping (AddedAsset) -> Void, isAssetSelected: @escaping (String) -> Bool) -> PhotoAttachmentPickerViewType func makeAttachmentMediaPickerView(options: AttachmentMediaPickerViewOptions) -> AttachmentMediaPickerViewType
func makeQuotedMessageAvatarView(for userDisplayInfo: UserDisplayInfo, size: CGSize) -> QuotedUserAvatar func makeUserAvatarView(options: UserAvatarViewOptions) -> UserAvatarViewType
func makeQuotedMessageContentView(options: QuotedMessageContentViewOptions) -> QuotedMessageContentViewType removed in v5, compose quoted-message rendering with func makeQuotedMessageView(options: QuotedMessageViewOptions) -> QuotedMessageViewType and func makeChatQuotedMessageView(options: ChatQuotedMessageViewOptions) -> ChatQuotedMessageViewType
func makeQuotedMessageHeaderView(quotedMessage: Binding<ChatMessage?>) -> QuotedMessageHeaderViewType func makeComposerQuotedMessageView(options: ComposerQuotedMessageViewOptions) -> ComposerQuotedMessageViewType
func makeQuotedMessageView(quotedMessage: ChatMessage, fillAvailableSpace: Bool, isInComposer: Bool, scrolledId: Binding<String?>) -> QuotedMessageViewType func makeQuotedMessageView(options: QuotedMessageViewOptions) -> QuotedMessageViewType
func makeReactionsBackgroundView(currentSnapshot: UIImage, popInAnimationInProgress: Bool) -> ReactionsBackground removed in v5, no direct replacement
func makeReactionsContentView(message: ChatMessage, contentRect: CGRect, onReactionTap: @escaping (MessageReactionType) -> Void) -> ReactionsContentView func makeReactionsContentView(options: ReactionsContentViewOptions) -> ReactionsContentView
func makeReactionsOverlayView(channel: ChatChannel, currentSnapshot: UIImage, messageDisplayInfo: MessageDisplayInfo, onBackgroundTap: @escaping () -> Void, onActionExecuted: @escaping (MessageActionInfo) -> Void) -> ReactionsOverlayViewType func makeReactionsOverlayView(options: ReactionsOverlayViewOptions) -> ReactionsOverlayViewType
func makeReactionsUsersView(message: ChatMessage, maxHeight: CGFloat) -> ReactionsUsersViewType func makeReactionsDetailView(options: ReactionsDetailViewOptions) -> ReactionsDetailViewType
func makeScrollToBottomButton(unreadCount: Int, onScrollToBottom: @escaping () -> Void) -> ScrollToBottomButtonType func makeScrollToBottomButton(options: ScrollToBottomButtonOptions) -> ScrollToBottomButtonType
func makeSendInChannelView(showReplyInChannel: Binding<Bool>, isDirectMessage: Bool) -> SendInChannelViewType func makeSendInChannelView(options: SendInChannelViewOptions) -> SendInChannelViewType
func makeSystemMessageView(message: ChatMessage) -> SystemMessageViewType func makeSystemMessageView(options: SystemMessageViewOptions) -> SystemMessageViewType
func makeTrailingComposerView(enabled: Bool, cooldownDuration: Int, onTap: @escaping () -> Void) -> TrailingComposerViewType func makeTrailingComposerView(options: TrailingComposerViewOptions) -> TrailingComposerViewType
func makeTypingIndicatorBottomView(channel: ChatChannel, currentUserId: UserId?) -> TypingIndicatorBottomViewType func makeInlineTypingIndicatorView(options: TypingIndicatorViewOptions) -> InlineTypingIndicatorViewType
func makeTypingIndicatorBottomView(channel: ChatChannel, currentUserId: UserId?) -> TypingIndicatorBottomViewType func makeSubtitleTypingIndicatorView(options: SubtitleTypingIndicatorViewOptions) -> SubtitleTypingIndicatorViewType
func makeVideoAttachmentView(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat, scrolledId: Binding<String?>) -> VideoAttachmentViewType func makeVideoAttachmentView(options: VideoAttachmentViewOptions) -> VideoAttachmentViewType
func makeVideoPlayerFooterView(attachment: ChatMessageVideoAttachment, shown: Binding<Bool>) -> VideoPlayerFooterViewType use func makeMediaViewerFooterView(options: MediaViewerFooterViewOptions) -> MediaViewerFooterViewType — the full-screen viewer is shared between images and videos in v5
func makeVideoPlayerHeaderView(title: String, subtitle: String, shown: Binding<Bool>) -> VideoPlayerHeaderViewType use func makeMediaViewerToolbarModifier(options: MediaViewerToolbarModifierOptions) -> MediaViewerToolbarModifierType — the full-screen viewer is shared between images and videos in v5
func makeVideoPlayerView(attachment: ChatMessageVideoAttachment, message: ChatMessage, isShown: Binding<Bool>, options: MediaViewsOptions) -> VideoPlayerViewType removed in v5, override func makeMediaViewer(options: MediaViewerOptions) -> MediaViewerType to provide a custom full-screen viewer (the same viewer renders images and videos), with func makeMediaViewerToolbarModifier(options: MediaViewerToolbarModifierOptions) -> MediaViewerToolbarModifierType and func makeMediaViewerFooterView(options: MediaViewerFooterViewOptions) -> MediaViewerFooterViewType for chrome
func makeVoiceRecordingView(for message: ChatMessage, isFirst: Bool, availableWidth: CGFloat, scrolledId: Binding<String?>) -> VoiceRecordingViewType func makeVoiceRecordingView(options: VoiceRecordingViewOptions) -> VoiceRecordingViewType
func navigationBarDisplayMode() -> NavigationBarItem.TitleDisplayMode moved to Utils.channelListConfig.navigationBarDisplayMode and Utils.messageListConfig.navigationBarDisplayMode
func supportedMessageActions(for message: ChatMessage, channel: ChatChannel, onFinish: @escaping (MessageActionInfo) -> Void, onError: @escaping (Error) -> Void) -> [MessageAction] moved to Utils.messageListConfig.supportedMessageActions closure
no v4 equivalent func makeAttachmentCommandsPickerView(options: AttachmentCommandsPickerViewOptions) -> AttachmentCommandsPickerViewType
no v4 equivalent func makeChatQuotedMessageView(options: ChatQuotedMessageViewOptions) -> ChatQuotedMessageViewType
no v4 equivalent func makeComposerInputTrailingView(options: ComposerInputTrailingViewOptions) -> ComposerInputTrailingViewType
no v4 equivalent func makeConfirmEditButton(options: ConfirmEditButtonOptions) -> ConfirmEditButtonType
no v4 equivalent func makeMessageAttachmentPreviewIconView(options: MessageAttachmentPreviewIconViewOptions) -> MessageAttachmentPreviewIconViewType
no v4 equivalent func makeMessageAttachmentPreviewThumbnailView(options: MessageAttachmentPreviewViewOptions) -> MessageAttachmentPreviewViewType
no v4 equivalent func makeMessageAttachmentsView(options: MessageAttachmentsViewOptions) -> MessageAttachmentsViewType
no v4 equivalent func makeMessageTopView(options: MessageTopViewOptions) -> MessageTopViewType
no v4 equivalent func makeMoreReactionsView(options: MoreReactionsViewOptions) -> MoreReactionsViewType
no v4 equivalent func makeSendMessageButton(options: SendMessageButtonOptions) -> SendMessageButtonType
no v4 equivalent func makeThreadRepliesDividerView(options: ThreadRepliesDividerViewOptions) -> ThreadRepliesDividerType
Polls
v4 v5
func makeComposerPollView(channelController: ChatChannelController, messageController: ChatMessageController?) -> ComposerPollViewType func makeAttachmentPollPickerView(options: AttachmentPollPickerViewOptions) -> AttachmentPollPickerViewType
func makePollView(message: ChatMessage, poll: Poll, isFirst: Bool) -> PollViewType func makePollView(options: PollViewOptions) -> PollViewType
Thread List
v4 v5
func makeNoThreadsView() -> NoThreads func makeEmptyThreadsView(options: EmptyThreadsViewOptions) -> EmptyThreads
func makeThreadDestination() -> (ChatThread) -> ThreadDestination func makeThreadDestination(options: ThreadDestinationOptions) -> @MainActor (ChatThread) -> ThreadDestination
func makeThreadListBackground(colors: ColorPalette) -> ThreadListBackground func makeThreadListBackground(options: ThreadListBackgroundOptions) -> ThreadListBackground
func makeThreadListContainerViewModifier(viewModel: ChatThreadListViewModel) -> ThreadListContainerModifier func makeThreadListContainerViewModifier(options: ThreadListContainerModifierOptions) -> ThreadListContainerModifier
func makeThreadListDividerItem() -> ThreadListDividerItem func makeThreadListDividerItem(options: ThreadListDividerItemOptions) -> ThreadListDividerItem
func makeThreadListFooterView(viewModel: ChatThreadListViewModel) -> ThreadListFooterView func makeThreadListFooterView(options: ThreadListFooterViewOptions) -> ThreadListFooterView
func makeThreadListHeaderView(viewModel: ChatThreadListViewModel) -> ThreadListHeaderView func makeThreadListHeaderView(options: ThreadListHeaderViewOptions) -> ThreadListHeaderView
func makeThreadListHeaderViewModifier(title: String) -> ThreadListHeaderViewModifier func makeThreadListHeaderViewModifier(options: ThreadListHeaderViewModifierOptions) -> ThreadListHeaderViewModifier
func makeThreadListItem(thread: ChatThread, threadDestination: @escaping (ChatThread) -> ThreadDestination, selectedThread: Binding<ThreadSelectionInfo?>) -> ThreadListItemType func makeThreadListItem(options: ThreadListItemOptions<ThreadDestination>) -> ThreadListItemType
func makeThreadListItemBackground(thread: ChatThread, isSelected: Bool) -> ThreadListItemBackground func makeThreadListItemBackground(options: ThreadListItemBackgroundOptions) -> ThreadListItemBackground
func makeThreadListLoadingView() -> ThreadListLoadingView func makeThreadListLoadingView(options: ThreadListLoadingViewOptions) -> ThreadListLoadingView
func makeThreadsListErrorBannerView(onRefreshAction: @escaping () -> Void) -> ThreadListErrorBannerView removed in v5, no direct replacement
Member Add And Text Rendering
v4 v5
func makeAddUsersView(options: AddUsersOptions, onUserTap: @escaping (ChatUser) -> Void) -> AddUsersViewType func makeMemberAddView(options: MemberAddViewOptions) -> MemberAddViewType
func makeAttachmentTextView(options: AttachmentTextViewOptions) -> AttachmentTextViewType func makeAttachmentTextView(options: AttachmentTextViewOptions) -> AttachmentTextViewType
no v4 equivalent func makeStreamTextView(options: StreamTextViewOptions) -> StreamTextViewType

The safest migration path is to compile your custom UI against v5, then manually compare every custom override against ViewFactory, Styles, ChannelListConfig, and MessageListConfig.

Do not rely on compile failures alone. v5 still provides broad default implementations, so stale v4-shaped methods can compile while silently falling back to the default behavior. Pay special attention to old leading swipe action overrides, custom attachment picker and preview overrides, factory-level makeMessageViewModifier(for:), and old supportedMoreChannelActions(...) / supportedMessageActions(...) factory methods.

Migration Checklist

  • Update your dependency to 5.x.
  • Replace Appearance(...) initializers with let appearance = Appearance() and property assignment.
  • Use Appearance.FontsSwiftUI() for SwiftUI font customization.
  • Move tint customizations to ColorPalette, usually accentPrimary and navigationBarTintColor.
  • Replace removed ColorPalette aliases with semantic v5 tokens such as textPrimary, backgroundCoreApp, chatBackgroundIncoming, and borderCoreDefault.
  • Move SwiftUI gradient message bubble customizations from ColorPalette arrays to a custom Styles.makeMessageViewModifier(for:) implementation that uses BubbleModifier(backgroundColors:).
  • Rename channelNamer to channelNameFormatter in your Utils initializer.
  • Replace ChatChannelNamer closure type with a class conforming to ChannelNameFormatter.
  • Replace DefaultChatChannelNamer with DefaultChannelNameFormatter.
  • Remove imageMerger, channelAvatarsMerger, and channelHeaderLoader from your Utils initializer.
  • Replace renamed reusable views: UserAvatar, ChannelAvatar, and TypingIndicatorView.
  • Update UserAvatar call sites: pass a ChatUser (or use the URL+initials initializer), use CGFloat for size, and switch showOnlineIndicator: Bool to the AvatarIndicator enum.
  • Replace MessageDisplayOptions(showAvatars:) with the new showIncomingMessageAvatar and showOutgoingMessageAvatar parameters; note that the new defaults differ (incoming true, outgoing false).
  • Migrate message container customizations from makeMessageContainerView(...) to makeMessageItemView(options:).
  • Migrate message translation/reply annotation customizations from makeMessageTranslationFooterView(...) and makeMessageRepliesShownInChannelView(...) to makeMessageTopView(options:) and makeMessageRepliesView(options:).
  • For custom text rendering in the message list, replace removed LinkDetectionTextView usage with ChatMessage.attributedTextContent(layoutDirection:translationLanguage:) by optionally changing attributed string attributes or content through configs
  • Migrate gallery, media, and video player customizations (makeGalleryView(...), makeGalleryHeaderView(...), makeVideoPlayerView(...), makeVideoPlayerHeaderView(...), makeVideoPlayerFooterView(...)) to makeMediaViewer(options:), makeMediaViewerToolbarModifier(options:), and makeMediaViewerFooterView(options:). The same MediaViewer now renders images and videos full-screen; there is no longer a separate video player factory.
  • Migrate quoted-message content customization from makeQuotedMessageContentView(...) to makeQuotedMessageView(options:) and/or makeChatQuotedMessageView(options:).
  • Move ViewFactory modifier overrides (makeChannelListContentModifier, makeChannelListModifier, makeMessageListModifier, makeMessageListContainerModifier, makeMessageViewModifier, makeComposerViewModifier) to a custom Styles implementation and assign it to factory.styles.
  • When implementing your custom Styles, conform to the Styles protocol directly — RegularStyles and LiquidGlassStyles are public class (not open), so you cannot subclass them from your own module. The protocol provides defaults for most methods, but you must implement makeComposerInputViewModifier(options:), makeComposerButtonViewModifier(options:), and makeSuggestionsContainerModifier(options:) (use RegularInputViewModifier, RegularButtonViewModifier, and SuggestionsRegularContainerModifier to match the default appearance).
  • If you customized channel-list search in v4, move that work out of makeChannelListTopView(...) and into Styles.makeSearchableModifier(options:). Treat makeChannelListTopView(options:) as a separate top content area in v5.
  • Move supportedMoreChannelActions to ChannelListConfig.supportedMoreChannelActions.
  • Move supportedMessageActions to MessageListConfig.supportedMessageActions.
  • Move navigationBarDisplayMode to ChannelListConfig.navigationBarDisplayMode and/or MessageListConfig.navigationBarDisplayMode; do not rely on the compatibility helper on DefaultViewFactory.
  • Update all ViewFactory overrides that take individual parameters to use the new single options object signature.
  • Review every custom ViewFactory override manually even if your project compiles; stale v4 methods can still compile while no longer overriding anything.
  • Pay special attention to old makeLeadingSwipeActionsView(...), custom attachment picker and preview overrides, factory-level makeMessageViewModifier(for:), and old supportedMoreChannelActions(...) / supportedMessageActions(...) methods.
  • Replace removed ViewFactory methods with their v5 equivalents (see the Channel And Message List table above).
  • Replace imageLoader and videoLoader on Utils with a single mediaLoader parameter.
  • Replace ImageLoading/NukeImageLoader conformances with MediaLoader/StreamMediaLoader.
  • Replace VideoLoading/VideoPreviewLoader with MediaLoader's loadVideoPreview method.
  • Replace CDNClient / AttachmentUploader with CDNStorage on ChatClientConfig.cdnStorage.
  • Replace ImageCDN / StreamImageCDN / FileCDN with CDNRequester / StreamCDNRequester configured through StreamMediaLoader(cdnRequester:).

After these changes, most v4 SwiftUI integrations should compile against v5, but custom factories still need a manual review to confirm each override is active and mapped to the correct v5 hook.

Add Chat to my app: getstream.io/SKILL.md

The fastest way to build with Stream. Start a new project or improve an existing one. Full CLI and documentation integration out of the box.


Ask your agent:

/stream Build me a Social App with Feeds and Moderation.
/stream Any livestream calls running?
/stream Chat iOS v5: <Your Question>