MarkdownToAttributedString converts a pragmatic subset of Markdown into NSAttributedString for iOS, macOS, tvOS, and watchOS.
It is designed for apps that need predictable rich text rendering without pulling in a heavy Markdown stack. The current implementation focuses on the elements that are most useful in app UI and knowledge-style content.
- Headers
#to###### - Paragraphs with inline formatting
- Bold, italic, bold+italic, and strikethrough
- Inline code and multiline fenced code blocks
- Ordered and unordered lists
- Indentation-based nested list rendering
- Blockquotes with multiline content
- Links rendered with the
.linkattribute - Image placeholders rendered as text
- Custom fonts, weights, italic style, and foreground colors
- Public parser and converter APIs
Add the package in Swift Package Manager:
dependencies: [
.package(url: "https://github.com/SergeiKriukov/MarkdownToAttributedString.git", from: "1.0.0")
]import MarkdownToAttributedString
let markdown = """
# Welcome
This is **bold**, *italic*, and `inline code`.
> A blockquote with [a link](https://example.com)
- First item
- Nested item
"""
let attributedString = markdown.toAttributedString()Use MarkdownConfiguration to override the styles used for each Markdown element.
import MarkdownToAttributedString
let configuration = MarkdownConfiguration(
text: .init(fontSize: 14),
h1: .init(fontSize: 28, fontWeight: .bold),
h2: .init(fontSize: 22, fontWeight: .semibold),
bold: .init(fontWeight: .bold),
italic: .init(isItalic: true),
code: .init(fontSize: 13, fontWeight: .medium),
listPrefix: .init(fontWeight: .semibold)
)
let attributedString = "# Styled title".toAttributedString(configuration: configuration)MarkdownStyle currently supports:
fontSizefontWeightisItalicforegroundColor
The available font weights are:
.regular.medium.semibold.bold.light
import MarkdownToAttributedString
#if canImport(UIKit)
import UIKit
let configuration = MarkdownConfiguration(
h1: .init(fontSize: 28, fontWeight: .bold, foregroundColor: .systemBlue),
link: .init(foregroundColor: .systemRed)
)
#elseif canImport(AppKit)
import AppKit
let configuration = MarkdownConfiguration(
h1: .init(fontSize: 28, fontWeight: .bold, foregroundColor: .systemBlue),
link: .init(foregroundColor: .systemRed)
)
#endifYou can use the high-level facade or the low-level parser/converter types directly.
import MarkdownToAttributedString
let parser = MarkdownParser()
let elements = parser.parse("# Title\nParagraph with **bold** text.")
let converter = AttributedStringConverter(configuration: .default)
let attributedString = converter.convert(elements)Or with the facade:
import MarkdownToAttributedString
let engine = MarkdownToAttributedString(configuration: .default)
let attributedString = engine.convert("Visit [Apple](https://apple.com)")| Element | Example | Status |
|---|---|---|
| Headers | # Title |
Supported |
| Paragraphs | plain text blocks | Supported |
| Bold | **bold** |
Supported |
| Italic | *italic* |
Supported |
| Bold + italic | ***value*** |
Supported |
| Strikethrough | ~~text~~ |
Supported |
| Inline code | `code` |
Supported |
| Fenced code blocks | ```swift ... ``` |
Supported |
| Ordered lists | 1. item |
Supported |
| Unordered lists | - item |
Supported |
| Nested list indentation | two-space indentation | Supported |
| Blockquotes | > quote |
Supported |
| Links | [title](url) |
Supported |
| Images |  |
Text placeholder |
| Tables | ` | a |
- Wrapped paragraph lines are joined with spaces.
- Consecutive blockquote lines are grouped into one rendered block.
- Images are rendered as a placeholder like
🖼️ Alt text. - Tables are currently treated as plain text because there is no table parser yet.
MarkdownParser.parse(_:)now returns block-level elements with richer metadata likechildren,info, andnestingLevel.
The result is a normal NSAttributedString, so you can export it to RTF or plain text using standard Foundation APIs.
let attributedString = markdown.toAttributedString()
let rtfData = try attributedString.data(
from: NSRange(location: 0, length: attributedString.length),
documentAttributes: [.documentType: NSAttributedString.DocumentType.rtf]
)
let plainText = attributedString.stringRun the test suite with:
swift testThe package includes:
- smoke tests for basic conversion;
- parser contract tests for block-level structure;
- rendering regression tests for links, fenced code blocks, nested formatting, and block separation.
The next practical improvements are:
- better blockquote prefix rendering for each quoted line;
- richer image handling via a resolver or attachment builder;
- table parsing and rendering;
- more advanced Markdown compatibility where it adds real UI value.
Contributions are welcome. Please keep changes small, add focused tests, and make sure examples and API docs stay in sync with the implementation.
MIT. See LICENSE.