How to implement syntax highlighting in SwiftUI
TL;DR
Build a AttributedString from your code string, iterate over Swift Regex matches for each token type, tint each run's foregroundColor, then render the whole thing with a plain Text view.
var result = AttributedString(code)
let keywords = #/\b(func|var|let|struct|class|enum|if|else|return|guard|import)\b/#
for match in code.matches(of: keywords) {
if let r = Range(match.range, in: result) {
result[r].foregroundColor = .purple
}
}
// Render:
Text(result).font(.system(.body, design: .monospaced))
Full implementation
import SwiftUI
// MARK: – Highlighter
struct SyntaxHighlighter {
private static let rules: [(Regex<Substring>, Color)] = [
(#/\b(func|var|let|struct|class|enum|protocol|extension|import|if|else|guard|return|switch|case|for|in|while|break|continue|true|false|nil|self|init|throws|async|await)\b/#, .purple),
(#/"(?:[^"\\]|\\.)*"/#, Color(red: 0.75, green: 0.1, blue: 0.1)),
(#/\/\/[^\n]*/#, .gray),
(#/\b\d+(?:\.\d+)?\b/#, .blue),
(#/\b[A-Z][A-Za-z0-9]*\b/#, Color(red: 0.1, green: 0.45, blue: 0.65)),
]
static func highlight(_ code: String) -> AttributedString {
var result = AttributedString(code)
for (pattern, color) in rules {
for match in code.matches(of: pattern) {
guard let attrRange = Range(match.range, in: result) else { continue }
result[attrRange].foregroundColor = color
}
}
return result
}
}
// MARK: – View
struct SyntaxHighlightedView: View {
let code: String
var body: some View {
ScrollView([.horizontal, .vertical]) {
Text(SyntaxHighlighter.highlight(code))
.font(.system(.footnote, design: .monospaced))
.lineSpacing(4)
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
}
.background(Color(white: 0.10))
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
// MARK: – Preview
#Preview {
SyntaxHighlightedView(code: """
import SwiftUI
// Entry point
struct ContentView: View {
let greeting = "Hello, world"
var count: Int = 42
var body: some View {
Text(greeting)
.font(.title)
}
}
""")
.padding()
.preferredColorScheme(.dark)
}
How it works
- Wrap in AttributedString.
AttributedString(code)creates a mutable attributed string whose character storage mirrors the originalString, preserving all indices soRange<String.Index>values can be bridged directly. - Match with Swift Regex literals. The
#/…/#literal syntax (Swift 5.7+, iOS 16+) gives you typed, compile-time-checked patterns.String.matches(of:)returns every non-overlapping match in order, which matters when later token types (e.g., type names) would otherwise re-color keyword runs. - Bridge ranges and tint.
Range(match.range, in: result)converts aRange<String.Index>to the equivalentRange<AttributedString.Index>. The subscriptresult[attrRange].foregroundColorwrites directly into that character run. - Render for free with Text. SwiftUI's
Textnatively rendersAttributedString, so noUIViewRepresentablewrapper is required. Pair with aScrollViewfor both axes to handle wide code lines.
Common pitfalls
- Rule order matters. Process comment and string-literal rules after keyword rules so that keywords inside strings/comments are not colored purple — or reverse the order and let later rules overwrite earlier ones deliberately.
- Range bridging returns nil for out-of-bounds slices. Always use
guard letwhen callingRange(match.range, in: result); multi-scalar emoji or malformed UTF-8 can produce ranges that don't align toAttributedStringboundaries. - Highlighting is synchronous and can block the main thread. For files longer than ~500 lines, run
SyntaxHighlighter.highlight(_:)inside aTask { await MainActor.run { … } }block and show a plainTextuntil the result is ready.
Claude Code prompt to generate this
Write a SwiftUI view called SyntaxHighlightedView that takes a plain Swift source string and renders it with syntax highlighting. Use AttributedString for the attributed text and Swift Regex literals (#/…/#) for pattern matching. Apply distinct colors for: keywords (purple), string literals (dark red), line comments (gray), numeric literals (blue), and capitalized type names (teal). Render with Text() in a monospaced font inside a dark-background ScrollView that scrolls both horizontally and vertically. Target iOS 17+, Swift 5.10. Include a #Preview showing a short sample Swift snippet.
Related guides
- How to render Markdown text in SwiftUI
- How to use AttributedString for rich text
- How to build a code editor with TextEditor
- How to use Swift Regex for text parsing
FAQ
Does this work on iOS 16?
The #/…/# Regex literal syntax and String.matches(of:) were introduced in Swift 5.7 / iOS 16, while AttributedString itself shipped in iOS 15. If you must support iOS 15, replace the Regex literals with NSRegularExpression and bridge results through NSRange → Range<String.Index> → Range<AttributedString.Index>. The iOS 17+ minimum lets you use the full modern stack as shown above.
What's the UIKit equivalent?
In UIKit, use NSAttributedString with NSRegularExpression to build a styled attributed string and display it in a UITextView with isEditable = false and isScrollEnabled = true. You can wrap this in SwiftUI via UIViewRepresentable when you need features like text selection or find-in-page that Text does not yet expose.
Last reviewed: 2026-05-12 by the Soarias team.