MarkdownDisplayView

repository·master·Indexed 41 days ago

https://github.com/zjc19891106/markdowndisplayview

A high-performance, 100% native UIKit Markdown renderer for iOS built on TextKit 2. Optimized for AI chat interfaces, it supports real-time SSE streaming, LaTeX math, chemical formulas, and complex STEM typesetting without using WebViews. Features include a native LaTeX lexer, Table of Contents (TOC) generation, syntax highlighting, and a dedicated streaming API for LLM content.

Tokens
13K
Snippets
37
Records
53
Agent score
51%

What's inside MarkdownDisplayView

  1. Overview of MarkdownDisplayView

    master

    MarkdownDisplayView is a high-performance, 100% native iOS UIKit component for rendering Markdown using TextKit 2. It is designed for zero-WebView overhead, providing instant loading and low memory usage.

    Key Capabilities:

    • Native LaTeX & Chemistry Support: Includes a custom recursive descent parser and CoreGraphics/CoreText engine for math (calculus, matrices, etc.), inorganic chemistry (\ce), and organic chemistry structures (\chemfig).
    • AI/SSE Streaming: Optimized for real-time AI chat with millisecond-level incremental UI updates and a buffering module to prevent flickering during complex formula rendering.
    • Markdown Compliance: Supports headers, lists, tables, quotes, images, footnotes, collapsible blocks, and horizontally scrollable code blocks.
    • Code Highlighting: Built-in support for over 20 common programming languages.
    • Navigation: Automatic Table of Contents (TOC), title jumping, and document anchors.
    • Theming: Supports custom fonts, colors, spacing, and light/dark mode presets.
  2. How streaming rendering works

    master

    The streaming engine is designed to handle the 'fragmented' nature of LLM outputs. It uses an internal buffer to manage incomplete Markdown syntax to prevent UI flickering or broken layouts.

    Key Features:

    • Smart Buffering: Automatically buffers unclosed structures like code blocks, tables, or LaTeX formulas until they are complete.
    • Incremental Rendering: Complete modules (like a finished paragraph) are rendered immediately, while incomplete ones stay in the buffer.
    • Pure Text Optimization: For content without Markdown markers, the system can commit updates at \n boundaries rather than waiting for \n\n, resulting in faster perceived output.
    • Safe Boundaries: Prioritizes committing complete header modules; if a header is incomplete, it falls back to paragraph boundaries.
    • Typewriter Effect: Provides smooth, character-by-character animation for the rendered content.
  3. Extend Markdown with custom syntax and renderers

    master

    The core library supports custom extensions to integrate business-specific Markdown syntax and rendering methods. Extensions are not automatically registered and must be implemented and registered by the consumer.

    Extension Types

    1. Custom Syntax (Inline/Block): Uses MarkdownCustomParser and MarkdownCustomViewProvider to handle specific syntax (e.g., [video:filename] or <echarts>...</echarts>).
    2. Custom Code Block Renderers: Implements MarkdownCodeBlockRenderer to handle specific programming or diagramming languages (e.g., ```mermaid).

    Registration Patterns

    • For Syntax Extensions: Use MarkdownCustomExtensionManager.shared to register your custom implementation (e.g., registerVideoExtension()).
    • For Code Block Renderers: Use MarkdownCustomExtensionManager.shared.register(codeBlockRenderer:).
    // Example: Registering a code block renderer
    let manager = MarkdownCustomExtensionManager.shared
    manager.register(codeBlockRenderer: MermaidRenderer())
  4. Optimize Memory for Static Documents

    master
    For long, static Markdown documents, you can optimize memory usage by enabling viewport virtualization. This ensures that only the visible views' backing stores are resident in memory, while offscreen complex views (like LaTeX formulas, tables, and code blocks) have their CALayer backing stores released while preserving their measured geometry and interaction state.
  5. Render LaTeX and Chemistry formulas

    master

    The component includes a native LaTeX lexer and parser that supports mathematical and chemical notation without external JS engines.

    Math Syntax

    • Inline math: Wrap in single dollars $E=mc^2$. Note: $math$ inside backticks (inline code) is treated as literal text.
    • Display math: Wrap in double dollars $$...$$ for centered, full-width blocks with horizontal scrolling.
    • Fenced math: Use ```math for display formulas.

    Chemistry Syntax

    • Inorganic (\ce{...}): Supports reaction equations, ionic charges, and equilibrium arrows (e.g., $$\ce{2H2 + O2 -> 2H2O}$$).
    • Organic (\chemfig{...}): Supports vector-rendered chemical structures like benzene rings and complex substituents (e.g., $$\chemfig{**6(------)}$$ ).
  6. Configure LaTeX and Chemistry formulas

    master

    The component features a native LaTeX and Chemistry parser that does not rely on WebViews or JS engines. It supports standard LaTeX syntax and mhchem for chemistry.

    LaTeX Syntax

    • Inline: $E=mc^2$
    • Block: $$ ... $$ (centered, supports horizontal scrolling for wide formulas)
    • Math Fences: ```math renders as a block formula; ```latex renders as raw code.

    Chemistry Support

    • mhchem: Supports \ce{...} for molecular formulas, ions, and reactions (e.g., \ce{2H2 + O2 -> 2H2O}).
    • chemfig: Supports \chemfig{...} for native CoreGraphics vector drawing of organic structures (e.g., benzene rings, complex substituents).

    LaTeX Configuration Properties

    • latexFontSize: Font size (default: 22)
    • latexAlignment: Alignment (.left, .center, .right)
    • latexTextColor: Text and line color
    • latexBackgroundColor: Background color
    • latexPadding: Internal padding (default: 20)
  7. How LaTeX rendering works in MarkdownDisplayView

    master

    The LaTeX integration follows this lifecycle:

    1. Detection: MarkdownParser scans the input for $$...$$ syntax.
    2. Element Creation: The parser creates a .latex(String) element in MarkdownRenderElement.
    3. View Generation: MarkdownDisplayView.createLatexView() is called to generate a dedicated view.
    4. Containerization: LatexMathView.createScrollableView() creates a scrollable container for the formula.
    5. Font Registration: FontLoader (a thread-safe singleton) automatically detects the environment (CocoaPods or SPM) and registers the required KaTeX fonts to the system during LatexMathView initialization.
  8. Implement Real-Time Streaming for LLM/SSE content

    master

    When displaying content from an AI model or Server-Sent Events (SSE), use the dedicated streaming API to ensure smooth rendering and proper handling of incomplete Markdown structures.

    Streaming Workflow

    All streaming calls must be made on the main thread. You must follow a strict lifecycle:

    1. beginRealStreaming(): Call once when the stream starts.
    2. appendStreamData(_:): Call for every decoded content delta as it arrives.
    3. endRealStreaming(): Call only after the server signals completion.

    Key Features

    • Smart Buffering: Automatically buffers incomplete structures like unclosed code blocks, tables, or LaTeX.
    • Incremental Rendering: Renders complete modules immediately while buffering incomplete ones.
    • Typewriter Effect: Provides smooth character-by-character animation.
    • Plain-Text Detection: Detects content without Markdown markers to allow faster submission at \n boundaries.
    @MainActor
    func streamDidStart(_ view: MarkdownViewTextKit) {
        view.beginRealStreaming()
    }
    
    @MainActor
    func streamDidReceive(_ delta: String, in view: MarkdownViewTextKit) {
        view.appendStreamData(delta)
    }
    
    @MainActor
    func streamDidFinish(_ view: MarkdownViewTextKit) {
        view.endRealStreaming()
    }
  9. Install MarkdownDisplayView via Swift Package Manager

    master

    You can install the library using Xcode's GUI or by modifying your Package.swift file.

    Option 1: Xcode GUI

    1. Open your project in Xcode.
    2. Select File → Add Package Dependencies....
    3. Enter the repository URL: https://github.com/zjc19891106/MarkdownDisplayView.git.
    4. Select your version requirement and click Add Package.

    Option 2: Package.swift Add the dependency to your Package.swift and include the product in your target. The package automatically resolves swift-markdown and Kingfisher dependencies.

    // In Package.swift
    dependencies: [
        .package(url: "https://github.com/zjc19891106/MarkdownDisplayView.git", from: "2.1.1")
    ]
    
    // In your target
    .target(
        name: "YourTarget",
        dependencies: [
            .product(name: "MarkdownDisplayView", package: "MarkdownDisplayView")
        ]
    )
  10. Implement real-time streaming for LLM/SSE

    master

    When receiving string chunks from an AI model or Server-Sent Events (SSE), use the dedicated streaming APIs to ensure smooth rendering and proper handling of incomplete Markdown structures (like unclosed code blocks or tables).

    Workflow:

    1. Call beginRealStreaming() before the first chunk arrives.
    2. Call appendStreamData(delta) for every incoming text fragment.
    3. Call endRealStreaming() when the stream is complete or the connection closes.

    Recommended Configuration for Chat Cells: Use .append typewriter mode to ensure smooth incremental updates.

    // 1. Streaming API usage
    @MainActor
    func streamDidStart(_ view: MarkdownViewTextKit) {
        view.beginRealStreaming()
    }
    
    @MainActor
    func streamDidReceive(_ delta: String, in view: MarkdownViewTextKit) {
        view.appendStreamData(delta)
    }
    
    @MainActor
    func streamDidFinish(_ view: MarkdownViewTextKit) {
        view.endRealStreaming()
    }
    
    // 2. Recommended configuration for AI chat
    var config = MarkdownConfiguration.default
    config.typewriterTextMode = .append
    config.typewriterHeightUpdateInterval = 20
    config.streamMinModuleLength = 10
    config.streamingHapticFeedbackStyle = .medium
    config.latexAlignment = .left
    scrollableMarkdownView.markdownView.configuration = config
  11. Basic Usage of ScrollableMarkdownViewTextKit

    master

    To render Markdown, instantiate ScrollableMarkdownViewTextKit, set its configuration to .default, and provide the Markdown string via the markdown property. Add the view to your hierarchy and constrain it like a standard UIScrollView.

    import UIKit
    import MarkdownDisplayView
    
    let markdownView = ScrollableMarkdownViewTextKit()
    markdownView.configuration = .default
    markdownView.markdown = "# Hello\n\nRender **Markdown** with TextKit 2."