# 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:

```swift
@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:

```swift
class CustomMessageResolver: MessageTypeResolving {

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

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

```swift
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):

```swift
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:

```swift
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:

```swift
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](https://github.com/GetStream/chat-ai-samples/blob/main/ios/AIComponents/TypingIndicatorHandler.swift).


---

This page was last updated at 2026-06-11T19:34:39.062Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/ios/ai-integrations/swiftui-integration/](https://getstream.io/chat/docs/sdk/ios/ai-integrations/swiftui-integration/).