Skip to content

keitaoouchi/MarkdownView

Repository files navigation

MarkdownView

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.

About

Markdown View for iOS.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors