A powerful Swift library for converting Markdown text to NSAttributedString with full formatting support. Perfect for iOS, macOS, tvOS, and watchOS applications that need to display rich text from Markdown sources.
- 🎯 Complete Markdown Support - Headers, bold, italic, strikethrough, code, lists, links, images, blockquotes
- 🎨 Customizable Styling - Configure fonts, sizes, colors, and weights for each element
- 📱 Cross-Platform - Works on iOS, macOS, tvOS, and watchOS
- 🚀 High Performance - Optimized parsing and conversion
- 🔧 Easy Integration - Simple API with Swift Package Manager support
- 📝 RTF Export - Convert to Rich Text Format for document sharing
- 📄 Plain Text Export - Extract clean text without formatting
Add MarkdownToAttributedString to your project using Swift Package Manager:
dependencies: [
.package(url: "https://github.com/SergeiKriukov/MarkdownToAttributedString.git", from: "1.0.0")
]import MarkdownToAttributedString
let markdown = """
# Welcome to MarkdownToAttributedString
This is **bold** and *italic* text with `inline code`.
You can also use ~~strikethrough~~ formatting.
## Features
- Easy to use
- Highly customizable
- Cross-platform support
> This is a blockquote with **bold** text inside.
"""
// Convert with default styling
let attributedString = markdown.toAttributedString()
// Or use custom configuration
let customConfig = MarkdownConfiguration(
h1: .init(fontSize: 28, fontWeight: .bold),
bold: .init(fontWeight: .bold, isItalic: false)
)
let customAttributedString = markdown.toAttributedString(configuration: customConfig)let config = MarkdownConfiguration(
// Header styles
h1: .init(fontSize: 24, fontWeight: .bold),
h2: .init(fontSize: 20, fontWeight: .semibold),
h3: .init(fontSize: 18, fontWeight: .medium),
// Text formatting
bold: .init(fontWeight: .bold),
italic: .init(isItalic: true),
strikethrough: .init(),
code: .init(fontSize: 14, fontWeight: .regular),
// List and blockquote styling
listPrefix: .init(fontWeight: .semibold),
blockquote: .init()
)
let attributedString = markdown.toAttributedString(configuration: config)fontSize- Text size in pointsfontWeight- Font weight (regular, bold, semibold, light)isItalic- Italic text stylingforegroundColor- Text color (iOS/macOS)
| Element | Example | Status |
|---|---|---|
| Headers | # H1, ## H2, ### H3 |
✅ |
| Bold | **bold text** |
✅ |
| Italic | *italic text* |
✅ |
| Strikethrough | ~~strikethrough~~ |
✅ |
| Inline Code | `code` |
✅ |
| Code Blocks | ```code``` |
✅ |
| Links | [text](url) |
✅ |
| Images |  |
✅ |
| Lists | - item, 1. item |
✅ |
| Blockquotes | > quote text |
✅ |
| Line Breaks | \n |
✅ |
import MarkdownToAttributedString
let parser = MarkdownParser()
let converter = AttributedStringConverter()
// Parse markdown into elements
let elements = parser.parse(markdown)
// Convert to attributed string
let attributedString = converter.convert(elements)let attributedString = markdown.toAttributedString()
// Export to RTF data
let rtfData = try attributedString.data(
from: NSRange(location: 0, length: attributedString.length),
documentAttributes: [.documentType: NSAttributedString.DocumentType.rtf]
)
// Save to file
try rtfData.write(to: fileURL)let attributedString = markdown.toAttributedString()
let plainText = attributedString.string
// Result: "Welcome to MarkdownToAttributedString\nThis is bold and italic text..."| Platform | Minimum Version | Status |
|---|---|---|
| iOS | 15.0+ | ✅ |
| macOS | 12.0+ | ✅ |
| tvOS | 15.0+ | ✅ |
| watchOS | 8.0+ | ✅ |
The library includes comprehensive tests for all supported Markdown elements:
swift testimport SwiftUI
import MarkdownToAttributedString
struct ContentView: View {
@State private var markdown = "# Hello World\nThis is **bold** text"
var body: some View {
VStack {
TextEditor(text: $markdown)
.frame(height: 200)
Text(AttributedString(markdown.toAttributedString()))
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}import AppKit
import MarkdownToAttributedString
class DocumentController: NSDocumentController {
func exportToRTF(_ markdown: String) {
let attributedString = markdown.toAttributedString()
let savePanel = NSSavePanel()
savePanel.allowedContentTypes = [.rtf]
savePanel.begin { response in
if response == .OK, let url = savePanel.url {
do {
let rtfData = try attributedString.data(
from: NSRange(location: 0, length: attributedString.length),
documentAttributes: [.documentType: NSAttributedString.DocumentType.rtf]
)
try rtfData.write(to: url)
} catch {
print("Export failed: \(error)")
}
}
}
}
}Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Clone the repository
- Open in Xcode or use Swift Package Manager
- Run tests:
swift test - Build the example:
swift build
This project is licensed under the MIT License - see the LICENSE file for details.
Автоматическая адаптация размера шрифта под системные настройки пользователя.
// Будущий API
let config = MarkdownConfiguration.dynamicType(
h1: .init(fontWeight: .bold),
body: .init(fontSize: .body) // .largeTitle, .title1, .body, .caption etc.
)
let attributedString = markdown.toAttributedString(
configuration: config,
traitCollection: UITraitCollection(preferredContentSizeCategory: .extraLarge)
)Реализация:
- Расширение
MarkdownStyleсFontSizeenum - Добавление метода
dynamicType()вMarkdownConfiguration - Интеграция с
UITraitCollectionдля автоматической адаптации
Возможность использовать кастомные маркеры вместо стандартных.
let config = MarkdownConfiguration(
listPrefix: .init(
unorderedSymbols: ["🍎", "🍌", "🍇"], // Для разных уровней вложенности
orderedStyle: .circledNumbers // .numbers, .letters, .roman, .circledNumbers
)
)
// Markdown: - Item 1\n - Item 2\n - Item 3
// Результат: 🍎 Item 1\n 🍌 Item 2\n 🍇 Item 3Реализация:
- Добавление
unorderedSymbolsиorderedStyleвMarkdownConfiguration - Расширение
MarkdownElementType.unorderedListс уровнем вложенности - Логика выбора символа по уровню в
AttributedStringConverter
Поддержка метаданных в начале Markdown файлов.
---
title: "My Document"
author: "John Doe"
date: "2025-10-04"
tags: ["markdown", "swift"]
---
# Document Content
This is the actual content...if let frontMatter = markdown.frontMatter {
print("Title: \(frontMatter["title"] ?? "")")
print("Author: \(frontMatter["author"] ?? "")")
}
// Или получение чистого контента без метаданных
let cleanContent = markdown.contentWithoutFrontMatterРеализация:
- Класс
FrontMatterParserдля парсинга YAML - Расширение
Stringс методамиfrontMatterиcontentWithoutFrontMatter - Поддержка различных форматов разделителей (---, +++)
Расширенный синтаксис для ссылок и изображений.
This is a [referenced link][1] and ![referenced image][2].
[1]: https://example.com "Optional title"
[2]: /path/to/image.png "Alt text"Реализация:
- Парсинг ссылок в конце документа
- Создание словаря соответствий
- Замена referenced синтаксиса на стандартный
Точный контроль типографики.
let config = MarkdownConfiguration(
h1: .init(lineSpacing: 8, paragraphSpacing: 16),
body: .init(lineSpacing: 4, paragraphSpacing: 8),
blockquote: .init(lineSpacing: 2, paragraphSpacing: 12)
)Реализация:
- Добавление
lineSpacingиparagraphSpacingвMarkdownStyle - Применение
NSMutableParagraphStyleвAttributedStringConverter
Поддержка изображений из app bundle.

let config = MarkdownConfiguration(
image: .init(
bundle: Bundle.main,
placeholder: "🖼️" // Для изображений, которые не найдены
)
)Реализация:
- Расширение
MarkdownElementType.imageс bundle поддержкой - Использование
NSTextAttachmentдля встраивания изображений - Fallback на placeholder для отсутствующих изображений
Базовая поддержка таблиц Markdown.
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |let config = MarkdownConfiguration(
table: .init(
headerStyle: .bold,
borderColor: .gray,
cellPadding: 8
)
)Реализация:
- Парсер для table синтаксиса
- Создание
NSTextTableдля табличного представления - Кастомизация стилей заголовков и ячеек
Расширяемая система правил для кастомных элементов.
// Создание кастомного правила
let mentionRule = CharacterRule(
pattern: "@(\\w+)",
style: .foregroundColor(.blue)
)
// Добавление в конфигурацию
let config = MarkdownConfiguration(
customRules: [mentionRule]
)
// Markdown: Hello @username!
// Результат: Hello @username! (с синим цветом)Реализация:
- Протокол
MarkdownRuleдля кастомных правил - Интеграция в основной парсер
- Поддержка регулярных выражений и стилей
Более точный контроль стилей шрифтов.
let config = MarkdownConfiguration(
h1: .init(fontStyle: .boldItalic, fontSize: 24),
code: .init(fontStyle: .monospaced),
link: .init(fontStyle: .underlined)
)Реализация:
- Enum
FontStyle(.normal, .bold, .italic, .boldItalic, .monospaced, .underlined) - Расширение
MarkdownStyleсfontStyleсвойством - Применение через
UIFontDescriptorиNSFontDescriptor
Полная поддержка тем и цветовых схем.
let lightConfig = MarkdownConfiguration.lightTheme()
let darkConfig = MarkdownConfiguration.darkTheme()
// Или кастомная тема
let customTheme = MarkdownTheme(
background: .systemBackground,
text: .label,
accent: .systemBlue
)Реализация:
- Класс
MarkdownThemeдля цветовых схем - Методы
lightTheme()иdarkTheme()вMarkdownConfiguration - Интеграция с
UITraitCollectionиNSAppearance
If you're looking for alternative Swift Markdown libraries, check out these excellent projects:
SwiftyMarkdown ⭐ 1.7k
- Author: Simon Fairbairn
- Focus: Comprehensive Markdown parsing with advanced customization
- Key Features:
- Rules-based processing engine
- Front Matter / YAML support
- Dynamic Type support
- SpriteKit integration
- Extensive styling options
- Platforms: iOS, macOS, tvOS, watchOS
MarkdownKit ⭐ 871
- Author: Bruno Oliveira
- Focus: Lightweight and extensible Markdown parser
- Key Features:
- Regular expression-based parsing
- Custom element support
- CocoaPods, Carthage, and SPM support
- Extensible architecture
- Platforms: iOS, macOS
Note: MarkdownToAttributedString was inspired by studying these projects and aims to provide a modern, Swift-first approach with comprehensive cross-platform support and detailed customization options.
- Inspired by the need for easy Markdown to NSAttributedString conversion
- Built with Swift's powerful string processing capabilities
- Thanks to the Swift community for excellent documentation and examples
- 🐛 Bug Reports: GitHub Issues
- 💡 Feature Requests: GitHub Discussions
- 📧 Contact: GitHub Profile
Made with ❤️ for the Swift community