SwiftUI Integration

The AI components work seamlessly with the StreamChatSwiftUI SDK.

Streaming Message View

You can easily integrate the message streaming view into the StreamChatSwiftUI SDK.

One option is to add it as a custom attachment:

@ViewBuilder
func makeCustomAttachmentViewType(options: CustomAttachmentViewTypeOptions) -> some View {
    let message = options.message
    let isGenerating = message.extraData["generating"]?.boolValue == true
    StreamingMessageView(
        content: message.text,
        isGenerating: isGenerating
    )
    .padding()
}

Then, you need to create your own message resolver that will tell the SDK to treat the messages with "ai_generated" custom data as custom attachments:

class CustomMessageResolver: MessageTypeResolving {

    func hasCustomAttachment(message: ChatMessage) -> Bool {
        message.extraData["ai_generated"] == true
    }
}

Finally, you need to inject this resolver when initializing the SDK:

let utils = Utils(messageTypeResolver: CustomMessageResolver())
let streamChat = StreamChat(chatClient: chatClient, utils: utils)

You can also add thinking indicators, for example at the bottom of the message list, using the makeMessageListContainerModifier from the Styles protocol.

Styles is a protocol with several methods that have default implementations and three that do not (makeComposerInputViewModifier, makeComposerButtonViewModifier, and makeSuggestionsContainerModifier). The SDK ships two concrete implementations (RegularStyles and LiquidGlassStyles) but they are not declared open, so you cannot subclass them from your own module — instead, conform to Styles directly and implement the three required methods using the public RegularInputViewModifier, RegularButtonViewModifier, and SuggestionsRegularContainerModifier types (or the LiquidGlass variants):

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

    init(typingIndicatorHandler: TypingIndicatorHandler) {
        self.typingIndicatorHandler = typingIndicatorHandler
    }

    func makeMessageListContainerModifier(
        options: MessageListContainerModifierOptions
    ) -> some ViewModifier {
        CustomMessageListContainerModifier(typingIndicatorHandler: typingIndicatorHandler)
    }

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

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

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

Assign it to your ViewFactory's styles property:

class CustomViewFactory: ViewFactory {
    @Injected(\.chatClient) public var chatClient
    let typingIndicatorHandler: TypingIndicatorHandler
    lazy var styles = CustomStyles(typingIndicatorHandler: typingIndicatorHandler)

    init(typingIndicatorHandler: TypingIndicatorHandler) {
        self.typingIndicatorHandler = typingIndicatorHandler
    }
    // ...
}

Where the CustomMessageListContainerModifier would look something like this:

struct CustomMessageListContainerModifier: ViewModifier {

    @ObservedObject var typingIndicatorHandler: TypingIndicatorHandler

    func body(content: Content) -> some View {
        content.overlay {
            AIAgentOverlayView(typingIndicatorHandler: typingIndicatorHandler)
        }
    }
}

struct AIAgentOverlayView: View {

    @ObservedObject var typingIndicatorHandler: TypingIndicatorHandler

    var body: some View {
        VStack {
            Spacer()
            if typingIndicatorHandler.typingIndicatorShown {
                HStack {
                    AITypingIndicatorView(text: typingIndicatorHandler.state)
                    Spacer()
                }
                .padding()
                .frame(height: 60)
                .background(Color(UIColor.secondarySystemBackground))
            }
        }
    }
}

The TypingIndicatorHandler can be an observable object that listens to the different events for the thinking state. For a reference implementation, please check our demo app.