Swift Markdown Kit · DOCS

Streaming & Chat

Streaming & Chat

Streaming

Drive the view controller directly from an LLM token stream:

renderer.startStreaming()
renderer.appendChunk("Streaming **Markdown")
renderer.appendChunk("** with math $x^2$", isFinal: true)

The renderer coalesces chunks via requestAnimationFrame and runs an incremental scanner that caches HTML up to the last safe block boundary, so total render cost stays linear in the size of the final answer rather than re-parsing everything on each update.

You can also bind a growing string to MarkdownRenderView: when the new value is a prefix extension of the old one, the view appends only the delta.

Chat mode

A whole conversation reuses the same rendering core through one WKWebView — not one WebView per message. During streaming only the current message updates.

let messages = [
    MarkdownChatMessage(id: "u1", role: .user, markdown: "Explain streaming."),
    MarkdownChatMessage(id: "a1", role: .assistant, markdown: "", status: .streaming)
]
let options = MarkdownChatRenderOptions(
    streamingPresentation: .smooth,
    messageActions: MarkdownChatMessageActionConfiguration(isEnabled: true)
)

MarkdownChatRenderView(messages: messages, options: options) { event in
    if case .messageAction(let action) = event {
        print(action.messageID, action.action)
    }
}

.smooth queues uneven network chunks for frame-by-frame display; use .immediate to show raw chunks as they arrive. For a native scroll-to-bottom control, hold a MarkdownChatRenderProxy and call scrollToBottom(animated:).

UIKit can drive the chat renderer directly:

let chat = MarkdownChatRenderViewController()
chat.setMessages(messages)
chat.appendChunk(messageID: "a1", chunk: "Streaming **Markdown")
chat.appendChunk(messageID: "a1", chunk: "** with math $x^2$", isFinal: true)