SwiftMath
SwiftMath provides a full Swift implementation of iosMath for displaying beautifully rendered math equations in iOS and MacOS applications. It typesets formulae written using LaTeX math mode in a UILabel equivalent class.
Install / Use
/learn @mgriebling/SwiftMathREADME
SwiftMath
SwiftMath provides a full Swift implementation of iosMath
for displaying beautifully rendered math equations in iOS and MacOS applications. It typesets formulae written
using LaTeX in a UILabel equivalent class. It uses the same typesetting rules as LaTeX and
so the equations are rendered exactly as LaTeX would render them.
Please also check out SwiftMathDemo for examples of how to use SwiftMath
from SwiftUI.
SwiftMath is similar to MathJax or
KaTeX for the web but for native iOS or MacOS
applications without having to use a UIWebView and Javascript. More
importantly, it is significantly faster than using a UIWebView.
SwiftMath is a Swift translation of the latest iosMath v0.9.5 release but includes bug fixes
and enhancements like a new \lbar (lambda bar) character and cyrillic alphabet support.
The original iosMath test suites have also been translated to Swift and run without errors.
Note: Error test conditions are ignored to avoid tagging everything with silly throws.
Please let me know of any bugs or bug fixes that you find.
SwiftMath prepackages everything needed for direct access via the Swift Package Manager.
Examples
Here are screenshots of some formulae that were rendered with this library:
x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}

f(x) = \int\limits_{-\infty}^\infty\!\hat f(\xi)\,e^{2 \pi i \xi x}\,\mathrm{d}\xi

\frac{1}{n}\sum_{i=1}^{n}x_i \geq \sqrt[n]{\prod_{i=1}^{n}x_i}

\frac{1}{\left(\sqrt{\phi \sqrt{5}}-\phi\\right) e^{\frac25 \pi}}
= 1+\frac{e^{-2\pi}} {1 +\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots} } } }

More examples are included in EXAMPLES
Fonts
Here are previews of the included fonts:

Requirements
SwiftMath works on iOS 11+ or MacOS 12+. It depends
on the following Apple frameworks:
- Foundation.framework
- CoreGraphics.framework
- QuartzCore.framework
- CoreText.framework
Additionally for iOS it requires:
- UIKit.framework
Additionally for MacOS it requires:
- AppKit.framework
Installation
Swift Package
SwiftMath is available from SwiftMath.
To use it in your code, just add the https://github.com/mgriebling/SwiftMath.git path to
XCode's package manager.
Usage
The library provides a class MTMathUILabel which is a UIView that
supports rendering math equations. To display an equation simply create
an MTMathUILabel as follows:
import SwiftMath
let label = MTMathUILabel()
label.latex = "x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}"
Adding MTMathUILabel as a sub-view of your UIView will render the
quadratic formula example shown above.
The following code creates a SwiftUI component called MathView encapsulating the MTMathUILabel:
import SwiftUI
import SwiftMath
struct MathView: UIViewRepresentable {
var equation: String
var font: MathFont = .latinModernFont
var textAlignment: MTTextAlignment = .center
var fontSize: CGFloat = 30
var labelMode: MTMathUILabelMode = .text
var insets: MTEdgeInsets = MTEdgeInsets()
func makeUIView(context: Context) -> MTMathUILabel {
let view = MTMathUILabel()
view.setContentHuggingPriority(.required, for: .vertical)
view.setContentCompressionResistancePriority(.required, for: .vertical)
return view
}
func updateUIView(_ view: MTMathUILabel, context: Context) {
view.latex = equation
let font = MTFontManager().font(withName: font.rawValue, size: fontSize)
font?.fallbackFont = UIFont.systemFont(ofSize: fontSize)
view.font = font
view.textAlignment = textAlignment
view.labelMode = labelMode
view.textColor = MTColor(Color.primary)
view.contentInsets = insets
view.invalidateIntrinsicContentSize()
}
func sizeThatFits(_ proposal: ProposedViewSize, uiView: MTMathUILabel, context: Context) -> CGSize? {
// Enable line wrapping by passing proposed width to the label
if let width = proposal.width, width.isFinite, width > 0 {
uiView.preferredMaxLayoutWidth = width
let size = uiView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
return size
}
return nil
}
}
For code that works with SwiftUI running natively under MacOS use the following:
import SwiftUI
import SwiftMath
struct MathView: NSViewRepresentable {
var equation: String
var font: MathFont = .latinModernFont
var textAlignment: MTTextAlignment = .center
var fontSize: CGFloat = 30
var labelMode: MTMathUILabelMode = .text
var insets: MTEdgeInsets = MTEdgeInsets()
func makeNSView(context: Context) -> MTMathUILabel {
let view = MTMathUILabel()
view.setContentHuggingPriority(.required, for: .vertical)
view.setContentCompressionResistancePriority(.required, for: .vertical)
return view
}
func updateNSView(_ view: MTMathUILabel, context: Context) {
view.latex = equation
let font = MTFontManager().font(withName: font.rawValue, size: fontSize)
font?.fallbackFont = NSFont.systemFont(ofSize: fontSize)
view.font = font
view.textAlignment = textAlignment
view.labelMode = labelMode
view.textColor = MTColor(Color.primary)
view.contentInsets = insets
view.invalidateIntrinsicContentSize()
}
func sizeThatFits(_ proposal: ProposedViewSize, nsView: MTMathUILabel, context: Context) -> CGSize? {
// Enable line wrapping by passing proposed width to the label
if let width = proposal.width, width.isFinite, width > 0 {
nsView.preferredMaxLayoutWidth = width
let size = nsView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
return size
}
return nil
}
}
Automatic Line Wrapping
SwiftMath supports automatic line wrapping (multiline display) for mathematical content. The implementation uses interatom line breaking which breaks equations at atom boundaries (between mathematical elements) rather than within them, preserving the semantic structure of the mathematics.
Using Line Wrapping with UIKit/AppKit
For direct MTMathUILabel usage, set the preferredMaxLayoutWidth property:
let label = MTMathUILabel()
label.latex = "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5"
label.font = MTFontManager.fontManager.defaultFont
// Enable line wrapping by setting a maximum width
label.preferredMaxLayoutWidth = 235
You can also use sizeThatFits to calculate the size with a width constraint:
let constrainedSize = label.sizeThatFits(CGSize(width: 235, height: .greatestFiniteMagnitude))
Using Line Wrapping with SwiftUI
The MathView examples above include sizeThatFits() which automatically enables line wrapping when SwiftUI proposes a width constraint. No additional configuration is needed:
VStack(alignment: .leading, spacing: 8) {
MathView(
equation: "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5",
fontSize: 17,
labelMode: .text
)
}
.frame(maxWidth: 235) // The equation will break across multiple lines
Line Wrapping Behavior and Capabilities
SwiftMath implements two complementary line breaking mechanisms:
1. Interatom Line Breaking (Primary)
Breaks equations between atoms (mathematical elements) when content exceeds the width constraint. This is the preferred method as it maintains semantic integrity.
2. Universal Line Breaking (Fallback)
For very long text within single atoms, breaks at Unicode word boundaries using Core Text with number protection (prevents splitting numbers like "3.14").
See MULTILINE_IMPLEMENTATION_NOTES.md for implementation details and recent changes.
Fully Supported Cases
These atom types work perfectly with interatom line breaking:
✅ Variables and ordinary text:
label.latex = "a b c d e f g h i j k l m n o p"
label.preferredMaxLayoutWidth = 150
// Breaks between individual variables at natural boundaries
✅ Binary operators (+, -, ×, ÷):
label.latex = "a+b+c+d+e+f+g+h"
label.preferredMaxLayoutWidth = 100
// Breaks cleanly: "a+b+c+d+"
// "e+f+g+h"
✅ Relations (=, <, >, ≤, ≥, etc.):
label.latex = "a=1, b=2, c=3, d=4, e=5"
label.preferredMaxLayoutWidth = 120
// Breaks after commas and operators
✅ Mixed text and simple math:
label.latex = "\\text{Calculer }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1"
label.preferredMaxLayoutWidth = 200
// Breaks between text and math atoms naturally
✅ Punctuation (commas, periods):
label.latex = "\\text{First, second, third, fourth, fifth}"
label.preferredMaxLayoutWidth = 150
// Breaks at commas and spaces
✅ Brackets and parentheses (simple):
label.latex = "(a+b)+(c+d)+(e+f)"
label.preferredMaxLayoutWidth = 120
// Breaks between parenthesized groups
✅ Greek letters and symbols:
labe
