GitHub

Swift 6.0 Swift Package Manager compatible

A WKWebView-based Markdown renderer for iOS. Converts Markdown to HTML using markdown-it with syntax highlighting by highlight.js.

GIF

Features

  • Renders Markdown as styled HTML inside a native UIView
  • Syntax highlighting for code blocks via highlight.js
  • Dark mode support (automatic, via prefers-color-scheme)
  • Custom CSS injection
  • markdown-it plugin support (e.g., KaTeX math)
  • External stylesheet loading
  • Intrinsic content size for Auto Layout integration
  • Link tap handling
  • SwiftUI support via MarkdownUI

Requirements

Target Version
iOS >= 16.0
Swift >= 6.0

Installation

MarkdownView is available through Swift Package Manager.

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/keitaoouchi/MarkdownView.git", from: "2.1.0")
]

Alternatively, you can add the package directly via Xcode (File > Add Package Dependencies).

Quick Start

UIKit

import MarkdownView
let md = MarkdownView()
md.reconfigure()
md.render(markdown: "# Hello World!")

SwiftUI

import SwiftUI
import MarkdownView
struct ContentView: View {
    var body: some View {
        ScrollView {
            MarkdownUI(body: "# Hello World!")
                .onTouchLink { request in
                    print(request.url ?? "")
                    return false
                }
                .onRendered { height in
                    print(height)
                }
        }
    }
}

API Reference

MarkdownView (UIKit)

Initializers

Signature Description
init() Creates a view with default settings. Call reconfigure() then render(markdown:) to display content.
init(css: String?, plugins: [String]?, stylesheets: [URL]? = nil, styled: Bool = true) Pre-configures a web view with CSS, plugins, and stylesheets. Use with render(markdown:) for efficient updates.
init?(coder: NSCoder) Interface Builder support.

Properties

Name Type Default Description
isScrollEnabled Bool true Controls whether the internal web view scrolls. Set false when embedding in a UIScrollView.
onTouchLink ((URLRequest) -> Bool)? nil Called when a link is tapped. Return true to allow navigation, false to cancel.
onRendered ((CGFloat) -> Void)? nil Called when rendering completes. The parameter is the content height in points.
intrinsicContentSize CGSize Returns the measured content height. Updates automatically after rendering.

Methods

Signature Description
reconfigure(css: String? = nil, plugins: [String]? = nil, stylesheets: [URL]? = nil, styled: Bool = true) Creates (or recreates) the internal web view with the given CSS, plugins, and stylesheets.
reconfigure(with: ConfigurationOptions) Same as above using a ConfigurationOptions struct.
render(markdown: String, options: RenderOptions = RenderOptions()) Renders Markdown on the current web view. If the web view is still loading, the request is queued and executed automatically when ready.

reconfigure vs render: reconfigure sets up the web view and styling. render sends Markdown to the already-configured web view. For dynamic content that changes frequently, call reconfigure once and then render for each update.

Deprecated: load(markdown:...) and show(markdown:) still work but are deprecated. Use reconfigure + render instead.

MarkdownUI (SwiftUI)

Initializer

MarkdownUI(
    body: String? = nil,
    css: String? = nil,
    plugins: [String]? = nil,
    stylesheets: [URL]? = nil,
    styled: Bool = true
)
Parameter Type Default Description
body String? nil The Markdown string to render.
css String? nil Custom CSS to inject.
plugins [String]? nil Array of markdown-it plugin JavaScript strings.
stylesheets [URL]? nil External stylesheet URLs to load.
styled Bool true Use the built-in Bootstrap-based stylesheet.

View Modifiers

Modifier Description
.onTouchLink(perform: @escaping (URLRequest) -> Bool) Called when a link is tapped. Return true to allow navigation, false to cancel.
.onRendered(perform: @escaping (CGFloat) -> Void) Called when rendering completes with the content height.

Note: MarkdownUI disables internal scrolling. Wrap it in a ScrollView for scrollable content.

Customization

Custom CSS

Inject a CSS string to override the default styles:

// UIKit
let css = "body { background-color: #f0f0f0; } code { font-size: 14px; }"
let md = MarkdownView(css: css, plugins: nil)
md.render(markdown: "# Styled content")
// SwiftUI
MarkdownUI(body: "# Styled content", css: "body { background-color: #f0f0f0; }")

See Example/Example/ViewController/CustomCss.swift for a full example.

Plugins

Add markdown-it compatible plugins by passing the plugin JavaScript as a string. Each plugin must be self-contained with no external dependencies.

let katexPlugin = try! String(contentsOfFile: Bundle.main.path(forResource: "katex", ofType: "js")!)
let md = MarkdownView(css: nil, plugins: [katexPlugin])
md.render(markdown: "Inline math: $E = mc^2$")

See Example/Example/ViewController/Plugins.swift for a full example, and the sample plugin project for building a compatible plugin library.

External Stylesheets

Load CSS from remote URLs:

let url = URL(string: "https://example.com/custom.css")!
let md = MarkdownView(css: nil, plugins: nil, stylesheets: [url])
md.render(markdown: "# Remote-styled content")

Styled vs Non-Styled Mode

By default, styled: true loads a Bootstrap-based stylesheet with highlight.js themes. Set styled: false to start with a blank canvas and apply your own CSS from scratch.

let md = MarkdownView(css: myCustomCSS, plugins: nil, styled: false)

Dark Mode

The built-in stylesheet supports dark mode automatically via prefers-color-scheme. Text and link colors adapt to the system appearance. No additional configuration is needed.

To customize dark mode styles, inject CSS with a prefers-color-scheme media query:

let css = """
@media (prefers-color-scheme: dark) {
    body { background-color: #1a1a1a; }
    code { color: #e06c75; }
}
"""
let md = MarkdownView(css: css, plugins: nil)

Example Project

The Example/ directory contains a full iOS app demonstrating UIKit usage, custom CSS, and plugin integration.

Architecture

See AGENTS.md for a high-level overview of the component architecture and data flow.

License

bootstrap is licensed under the MIT License. highlight.js is licensed under the BSD-3-Clause License. markdown-it is licensed under the MIT License.

MarkdownView is available under the MIT license. See the LICENSE file for more info.

Read the original on github.com ↗