Skip to main content

Overview

MarkdownView provides a native MarkdownTextView component that works seamlessly with UIKit on iOS, macOS (AppKit), and visionOS. The view handles markdown parsing, rendering, and user interactions like link taps and image handling.

Basic Setup

Here’s a complete example of integrating MarkdownTextView into a UIKit view controller:
import UIKit
import MarkdownParser
import MarkdownView

class ViewController: UIViewController {

    private let markdownView = MarkdownTextView()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(markdownView)
        markdownView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            markdownView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            markdownView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            markdownView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
        ])

        // Parse and render markdown
        let parser = MarkdownParser()
        let result = parser.parse("""
        # Hello, Markdown!

        This is **bold**, *italic*, and `inline code`.

        - Item one
        - Item two
        - Item three
        """)

        let content = MarkdownTextView.PreprocessedContent(
            parserResult: result,
            theme: .default
        )
        markdownView.setMarkdown(content)
    }
}

Key Components

1

Initialize MarkdownTextView

Create an instance of MarkdownTextView() and add it to your view hierarchy using Auto Layout:
private let markdownView = MarkdownTextView()

view.addSubview(markdownView)
markdownView.translatesAutoresizingMaskIntoConstraints = false
2

Parse Markdown Content

Use MarkdownParser to convert markdown strings into a parsed AST:
let parser = MarkdownParser()
let result = parser.parse(markdownString)
3

Render Parsed Content

Create a PreprocessedContent object with the parser result and theme, then set it on the view:
let content = MarkdownTextView.PreprocessedContent(
    parserResult: result,
    theme: .default
)
markdownView.setMarkdown(content)
MarkdownTextView provides a linkHandler closure for responding to link taps:
markdownView.linkHandler = { payload, range, point in
    switch payload {
    case .url(let url):
        UIApplication.shared.open(url)
    case .string(let string):
        print("Tapped link: \(string)")
    }
}
The linkHandler receives three parameters:
  • payload: Either a .url(URL) or .string(String) containing the link destination
  • range: The NSRange of the link text in the content
  • point: The CGPoint where the tap occurred

Handling Image Taps

You can respond to image taps using the imageTapHandler:
markdownView.imageTapHandler = { source, point in
    print("Image tapped: \(source)")
    // Show full-screen image viewer
}
The handler receives:
  • source: The image URL or path as a string
  • point: The tap location in the view’s coordinate system

Dynamic Updates

Updating with Preprocessed Content

For best performance when you’ve already parsed the markdown:
let content = MarkdownTextView.PreprocessedContent(
    parserResult: parserResult,
    theme: currentTheme
)
markdownView.setMarkdown(content)

Updating with Raw Markdown String

For convenience, you can also pass raw markdown strings. Parsing and syntax highlighting happen asynchronously on a background queue:
markdownView.setMarkdown(string: "# New Content\n\nUpdated text...")
When using setMarkdown(string:), any in-flight preprocessing is automatically cancelled when new content arrives, ensuring smooth performance during rapid updates.

Size and Layout

Intrinsic Content Size

MarkdownTextView calculates its intrinsic content size automatically:
let size = markdownView.intrinsicContentSize

Custom Width Constraints

To calculate the height needed for a specific width:
let width: CGFloat = 375
let boundingSize = markdownView.boundingSize(for: width)
print("Height needed: \(boundingSize.height)")

Performance Optimization

Throttling Updates

By default, updates are throttled to 20 fps. You can customize this:
// 60 fps for smoother updates
markdownView.throttleInterval = 1.0 / 60.0

// Disable throttling
markdownView.throttleInterval = nil

Scroll View Binding

For better text selection behavior when embedded in a scroll view:
markdownView.bindContentOffset(from: scrollView)

Code Preview Handler

Handle code block preview requests (when users tap the eye icon on code blocks):
markdownView.codePreviewHandler = { language, attributedText in
    let previewVC = CodePreviewViewController()
    previewVC.language = language
    previewVC.code = attributedText
    present(previewVC, animated: true)
}

Resetting Content

To clear all content and reset the view state:
markdownView.reset()

Accessibility

MarkdownTextView includes comprehensive VoiceOver support out of the box:
  • Text content is fully accessible
  • Code blocks are announced with their language
  • Tables are navigable
  • Math content is accessible
  • Link interactions work with VoiceOver
The view automatically sets isAccessibilityElement = false and accessibilityTraits = .staticText to ensure proper accessibility behavior.

Best Practices

Preprocess for Performance

When updating the same markdown repeatedly, preprocess once and reuse the PreprocessedContent object.

Handle Links Properly

Always implement linkHandler to provide proper navigation when users tap links.

Use Background Updates

Let the view handle background parsing by using setMarkdown(string:) for dynamic content.

Test Accessibility

Always test your markdown content with VoiceOver to ensure all information is accessible.

Build docs developers (and LLMs) love