MarkdownView provides a comprehensive theming system that goes beyond basic color and font customization. This guide covers advanced theming techniques including creating theme presets, font scaling, and fine-grained control over every visual element.
Theme Architecture
The MarkdownTheme struct is the central configuration object for all visual styling:
public struct MarkdownTheme: Equatable {
public var fonts: Fonts = .init()
public var colors: Colors = .init()
public var spacings: Spacings = .init()
public var sizes: Sizes = .init()
public var table: Table = .init()
public var image: Image = .init()
public init() {}
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:22
All theme properties are platform-adaptive, automatically using UIKit types on iOS/visionOS and AppKit types on macOS through the PlatformFont and PlatformColor type aliases.
Theme Properties
Fonts
The Fonts struct controls typography throughout the markdown document:
public struct Fonts: Equatable {
public var body: PlatformFont
public var codeInline: PlatformFont
public var bold: PlatformFont
public var italic: PlatformFont
public var code: PlatformFont
public var largeTitle: PlatformFont
public var title: PlatformFont
public var footnote: PlatformFont
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:23-54
Default values:
body: System body font
codeInline: Monospaced at body size
code: Monospaced at 85% of body size
bold: Bold variant of body font
italic: Italic variant of body font
largeTitle, title: Bold body font (scaled by heading level)
footnote: System footnote/small font
Colors
The Colors struct defines the color palette:
public struct Colors: Equatable {
public var body: PlatformColor
public var highlight: PlatformColor
public var emphasis: PlatformColor
public var code: PlatformColor
public var codeBackground: PlatformColor
public var selectionBackground: PlatformColor?
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:59-93
Default values:
body: System label color (adapts to light/dark mode)
highlight: Accent color or orange fallback
emphasis: Same as highlight
code: System label color
codeBackground: Gray with 25% alpha
selectionBackground: Accent color with 20% alpha
Spacings
Control margins and padding between elements:
public struct Spacings: Equatable {
public var final: CGFloat = 16
public var general: CGFloat = 8
public var list: CGFloat = 8
public var cell: CGFloat = 32
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:97-102
final: Spacing after the last block
general: Default spacing between blocks
list: Spacing between list items
cell: Minimum width for table cells
Sizes
Control the size of decorative elements:
public struct Sizes: Equatable {
public var bullet: CGFloat = 4
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:106-108
Table Styling
Comprehensive table appearance configuration:
public struct Table: Equatable {
public var cornerRadius: CGFloat = 8
public var borderWidth: CGFloat = 1
public var borderColor: PlatformColor
public var headerBackgroundColor: PlatformColor
public var cellBackgroundColor: PlatformColor
public var stripeCellBackgroundColor: PlatformColor
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:112-126
Image Rendering
Control how inline images are displayed:
public struct Image: Equatable {
public var cornerRadius: CGFloat = 4
public var maxWidthFraction: CGFloat = 1.0
public var placeholderColor: PlatformColor
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:130-138
cornerRadius: Rounded corners on images
maxWidthFraction: Maximum width as a fraction of available width (1.0 = 100%)
placeholderColor: Color shown while images load
Font Scaling System
MarkdownView provides a built-in font scaling system through the FontScale enum:
public enum FontScale: String, CaseIterable {
case tiny
case small
case middle
case large
case huge
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:168-174
Scale Offsets
Each scale level applies a point size offset:
var offset: Int {
switch self {
case .tiny: -4
case .small: -2
case .middle: 0
case .large: 2
case .huge: 4
}
}
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:178-186
The scaleFont(by:) Method
Apply a font scale to all theme fonts:
mutating func scaleFont(by scale: FontScale)
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:195
var theme = MarkdownTheme.default
theme.scaleFont(by: .large) // All fonts increase by 2pt
markdownView.theme = theme
This method scales all font properties (body, code, bold, italic, etc.) by the specified offset.
The scaleFont(by:) method resets fonts to their default values before applying the scale, ensuring consistent results.
The align(to:) Method
Align all theme fonts to a specific point size:
mutating func align(to pointSize: CGFloat)
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:206
var theme = MarkdownTheme.default
theme.align(to: 18.0) // All fonts use 18pt as base size
markdownView.theme = theme
This method:
- Sets
body font to the specified point size
- Maintains font characteristics (bold, italic, monospace)
- Scales
code fonts to 85% of the base size
- Preserves relative proportions between font types
Use align(to:) when you need precise control over absolute font sizes, and scaleFont(by:) when you want to adjust fonts relative to system defaults.
Creating Theme Presets
Basic Custom Theme
extension MarkdownTheme {
static var oceanic: MarkdownTheme {
var theme = MarkdownTheme()
// Fonts
theme.fonts.body = .systemFont(ofSize: 16)
theme.fonts.code = .monospacedSystemFont(ofSize: 14, weight: .regular)
// Colors
theme.colors.body = UIColor(red: 0.9, green: 0.9, blue: 0.95, alpha: 1.0)
theme.colors.highlight = UIColor(red: 0.2, green: 0.7, blue: 0.9, alpha: 1.0)
theme.colors.code = UIColor(red: 0.95, green: 0.95, blue: 0.7, alpha: 1.0)
theme.colors.codeBackground = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 1.0)
return theme
}
}
// Usage
markdownView.theme = .oceanic
Dark Mode Adaptive Theme
extension MarkdownTheme {
static var adaptiveProfessional: MarkdownTheme {
var theme = MarkdownTheme()
// Dynamic colors that adapt to appearance
theme.colors.body = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark
? UIColor(white: 0.95, alpha: 1.0)
: UIColor(white: 0.1, alpha: 1.0)
}
theme.colors.codeBackground = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark
? UIColor(red: 0.15, green: 0.15, blue: 0.17, alpha: 1.0)
: UIColor(red: 0.96, green: 0.96, blue: 0.97, alpha: 1.0)
}
return theme
}
}
High Contrast Theme
extension MarkdownTheme {
static var highContrast: MarkdownTheme {
var theme = MarkdownTheme()
// Increase font sizes
theme.align(to: 20.0)
// High contrast colors
theme.colors.body = .black
theme.colors.code = .black
theme.colors.codeBackground = .white
theme.colors.highlight = UIColor(red: 0.0, green: 0.3, blue: 0.8, alpha: 1.0)
// Increased spacing
theme.spacings.general = 16
theme.spacings.list = 12
// More prominent table borders
theme.table.borderWidth = 2
theme.table.borderColor = .black
return theme
}
}
Minimalist Theme
extension MarkdownTheme {
static var minimalist: MarkdownTheme {
var theme = MarkdownTheme()
// Serif fonts for a classic look
let serifFont = UIFont(name: "Georgia", size: 17) ?? .systemFont(ofSize: 17)
theme.fonts.body = serifFont
theme.fonts.bold = UIFont(name: "Georgia-Bold", size: 17) ?? serifFont
theme.fonts.italic = UIFont(name: "Georgia-Italic", size: 17) ?? serifFont
// Subtle colors
theme.colors.body = UIColor(white: 0.2, alpha: 1.0)
theme.colors.highlight = UIColor(white: 0.4, alpha: 1.0)
theme.colors.codeBackground = UIColor(white: 0.95, alpha: 1.0)
// Clean table style
theme.table.cornerRadius = 0
theme.table.borderColor = UIColor(white: 0.8, alpha: 1.0)
theme.table.stripeCellBackgroundColor = .clear
// Sharp image corners
theme.image.cornerRadius = 0
return theme
}
}
Advanced Customization
Dynamic Theme Switching
class DocumentViewController: UIViewController {
let markdownView = MarkdownTextView()
enum ThemeStyle {
case light, dark, sepia, highContrast
}
func applyTheme(_ style: ThemeStyle) {
let theme: MarkdownTheme
switch style {
case .light:
theme = .default
case .dark:
theme = .dark
case .sepia:
theme = .sepia
case .highContrast:
theme = .highContrast
}
UIView.animate(withDuration: 0.3) {
self.markdownView.theme = theme
}
}
}
User-Adjustable Font Size
class ReadingViewController: UIViewController {
let markdownView = MarkdownTextView()
var currentScale: MarkdownTheme.FontScale = .middle
@objc func increaseFontSize() {
switch currentScale {
case .tiny: currentScale = .small
case .small: currentScale = .middle
case .middle: currentScale = .large
case .large: currentScale = .huge
case .huge: return
}
updateTheme()
}
@objc func decreaseFontSize() {
switch currentScale {
case .tiny: return
case .small: currentScale = .tiny
case .middle: currentScale = .small
case .large: currentScale = .middle
case .huge: currentScale = .large
}
updateTheme()
}
private func updateTheme() {
var theme = MarkdownTheme.default
theme.scaleFont(by: currentScale)
markdownView.theme = theme
}
}
Accessibility-Aware Themes
extension MarkdownTheme {
static func accessible(preferredSize: UIFont.TextStyle = .body) -> MarkdownTheme {
var theme = MarkdownTheme()
// Use Dynamic Type
theme.fonts.body = .preferredFont(forTextStyle: preferredSize)
theme.fonts.code = UIFont.monospacedSystemFont(
ofSize: UIFont.preferredFont(forTextStyle: preferredSize).pointSize,
weight: .regular
)
// High contrast colors
theme.colors.body = .label
theme.colors.code = .label
theme.colors.codeBackground = .secondarySystemBackground
// Increased spacing for better readability
theme.spacings.general = 12
theme.spacings.list = 10
return theme
}
}
// Respond to Dynamic Type changes
NotificationCenter.default.addObserver(
forName: UIContentSizeCategory.didChangeNotification,
object: nil,
queue: .main
) { _ in
markdownView.theme = .accessible()
}
Platform-Specific Customization
Use conditional compilation for platform-specific themes:
extension MarkdownTheme {
static var platformOptimized: MarkdownTheme {
var theme = MarkdownTheme()
#if os(iOS) || os(visionOS)
// Touch-optimized spacing on iOS
theme.spacings.general = 12
theme.spacings.list = 10
theme.table.cornerRadius = 10
#elseif os(macOS)
// Denser layout for desktop
theme.spacings.general = 8
theme.spacings.list = 6
theme.table.cornerRadius = 6
#endif
return theme
}
}
Code Scale Constant
The default code font scaling factor is available as a constant:
public static let codeScale = 0.85
Location: Sources/MarkdownView/MarkdownTheme/MarkdownTheme.swift:19
Code fonts are typically rendered at 85% of the body font size for better visual hierarchy.
Theme Equality
MarkdownTheme conforms to Equatable, allowing you to compare themes:
let theme1 = MarkdownTheme.default
let theme2 = MarkdownTheme.default
if theme1 == theme2 {
print("Themes are identical")
}
This is useful for optimizing rendering when themes haven’t changed.
Best Practices
1. Create Reusable Presets
Define theme presets as static properties on MarkdownTheme extension:
extension MarkdownTheme {
static var myCustomTheme: MarkdownTheme { ... }
}
2. Use Dynamic Colors
Always use dynamic colors that adapt to light/dark mode for better user experience:
UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? darkColor : lightColor
}
3. Test Across Platforms
Verify your custom themes work correctly on all target platforms (iOS, macOS, visionOS).
4. Respect Accessibility Settings
When possible, use Dynamic Type and respect user preferences for text size and contrast.
5. Cache Theme Objects
Theme creation can involve font lookups and color creation. Cache reusable theme objects:
class ThemeManager {
static let shared = ThemeManager()
private(set) lazy var light = MarkdownTheme.light
private(set) lazy var dark = MarkdownTheme.dark
}
See Also