SyntaxEditorUI is a Swift package for building editable plain-text and syntax-highlighted code views on iOS and macOS.
It provides SwiftUI, UIKit, and AppKit entry points with built-in language support, editor shortcuts, and common code-editing behavior.
- Editable code views for SwiftUI, UIKit, and AppKit apps.
- Plain Text editing plus syntax highlighting for CSS, HTML, JavaScript, JSON, Objective-C, Swift, TOML, and XML.
- Embedded JavaScript and CSS highlighting inside HTML.
- Code-aware editing behavior for supported syntax-highlighted languages:
- bracket and quote auto-pairing
- smart newline indentation
- line indent and outdent
- comment toggling for supported languages
- pair-aware backspace deletion
- matching bracket highlight
- Keyboard shortcuts for common editor actions.
- iOS accessory controls for undo, redo, and keyboard dismissal.
- Programmatic control over text, language, editability, line wrapping, theme, font size, and background drawing.
- Swift 6.3+
- iOS 18+
- Mac Catalyst 18+
- macOS 15+
- visionOS 2+
Tab: Insert spaces at the caret; indent selected lines in syntax-highlighted language modesShift-Tab: Outdent in syntax-highlighted language modesCmd+]: Indent in syntax-highlighted language modesCmd+[: Outdent in syntax-highlighted language modesCmd+/: Toggle comment (HTML/JavaScript/CSS/Objective-C/Swift/TOML/XML)Ctrl+Shift+Cmd+L: Toggle line wrappingCmd++: Increase font sizeCmd+-: Decrease font sizeCtrl+Cmd+0: Reset font sizeCmd+Z: UndoShift+Cmd+Z: RedoCmd+F: FindCmd+G: Find nextShift+Cmd+G: Find previous
import SwiftUI
import SyntaxEditorUI
struct EditorView: View {
@State private var model = SyntaxEditorModel(
text: "const answer = 42;",
language: .javascript
)
var body: some View {
SyntaxEditor(model)
.onChange(of: model.text) {
print("Edited text:", model.text)
}
}
}import SyntaxEditorUI
let model = SyntaxEditorModel(
text: "const answer = 42;",
language: .javascript
)
let editorView = SyntaxEditorView(model: model)
let editorViewController = SyntaxEditorViewController(model: model)Use SyntaxLanguage.plainText when an editor should behave as strict plain text without syntax highlighting or code-aware editing transforms:
let notesModel = SyntaxEditorModel(text: "Notes", language: .plainText)Supported languages are available through SyntaxLanguage: Plain Text, CSS, HTML, JavaScript, JSON, Objective-C, Swift, TOML, and XML.
Use SyntaxLanguage(identifier:) when resolving user input or file metadata, and SyntaxLanguage.allCases when presenting every built-in language.
To move first-use highlighting setup out of the editor load path, prepare the languages your app expects to show:
Task.detached {
await SyntaxEditorHighlighting.prepare([.swift, .html])
}Set model.drawsBackground = false when the surrounding view should provide the editor background while syntax colors and editor decorations remain active. Use model.fontSizeDelta, increaseFontSize(), decreaseFontSize(), and resetFontSize() for Xcode-style point-size adjustments relative to the selected theme.
Use SyntaxEditorMenu when an app wants to expose editor shortcuts in an Editor menu. On iOS 26 and later, install it from the app delegate's main menu configuration:
if #available(iOS 26.0, *) {
UIMainMenuSystem.shared.setBuildConfiguration(UIMainMenuSystem.Configuration()) { builder in
SyntaxEditorMenu.insert(into: builder)
}
}On iPadOS, first-responder key commands can also appear under Help > Other Keyboard Shortcuts; install the Editor menu through the main menu builder when the commands should appear as a menu bar menu.
On macOS, insert the menu item into the app's main menu:
SyntaxEditorMenu.insert(into: NSApp.mainMenu!)Apps that use SyntaxEditorView on iPadOS should enable UIApplicationSupportsIndirectInputEvents in their Info.plist. With this key enabled, mouse and trackpad click-drags are handled by UIKit text selection instead of scroll dragging, while finger drag scrolling and trackpad or mouse wheel scrolling continue to work.
swift test
xcrun simctl list devices available
DESTINATION='platform=iOS Simulator,id=<simulator-udid>'
xcodebuild test -workspace SyntaxEditorUI.xcworkspace -scheme SyntaxEditorUITests -testPlan SyntaxEditorUITests -only-testing:SyntaxEditorCorePlatformTests -only-testing:SyntaxEditorUITests -destination "$DESTINATION" -enableCodeCoverage NO -parallel-testing-enabled NO -maximum-concurrent-test-simulator-destinations 1GitHub Actions runs swift test on macOS for package-wide coverage, then runs SyntaxEditorCorePlatformTests and SyntaxEditorUITests on the latest available iOS simulator for UIKit-specific coverage.
Mini is a lightweight manual verification app for iOS/macOS. It is not a public product and does not own package regression tests.
Highlighting performance benchmarks are exposed through the SwiftPM benchmark plugin:
swift package benchmark list --target HighlightBenchmark
swift package benchmark run --target HighlightBenchmark --filter 'fixture-swift-structural-edit/highlight/incremental-update' --time-units microseconds --no-progress
swift package --allow-writing-to-package-directory benchmark baseline update before --target HighlightBenchmark --filter 'fixture-swift-structural-edit/highlight/incremental-update'
swift package benchmark baseline compare before --target HighlightBenchmark --filter 'fixture-swift-structural-edit/highlight/incremental-update'
SYNTAX_EDITOR_BENCHMARK_FILE=/path/to/file.swift swift package benchmark run --target HighlightBenchmarkThe benchmark suite uses bundled reference samples by default and repeats them to around 10,000 lines. Large cases repeat to around 50,000 lines. Set SYNTAX_EDITOR_BENCHMARK_FILE to benchmark a custom file, and optionally set SYNTAX_EDITOR_BENCHMARK_LANGUAGE, SYNTAX_EDITOR_BENCHMARK_REPEAT_SOURCE, SYNTAX_EDITOR_BENCHMARK_ITERATIONS, SYNTAX_EDITOR_BENCHMARK_TYPING_EDITS, SYNTAX_EDITOR_BENCHMARK_TYPING_ANCHOR, SYNTAX_EDITOR_BENCHMARK_TYPE_TEXT, SYNTAX_EDITOR_BENCHMARK_TYPE_AFTER, or SYNTAX_EDITOR_BENCHMARK_TYPE_REPEAT to adjust the run. SYNTAX_EDITOR_BENCHMARK_REPEAT_SOURCE overrides the default sample amplification.
Benchmarks are intended for local development and are not part of regular CI. Performance regression checks should run on a dedicated machine or a manually triggered workflow to avoid shared-runner noise.
These notes apply when upgrading from v0.14.x or earlier to v0.15.0.
SyntaxEditorTextEdithas been replaced bySyntaxEditorTextChange.Replacement.SyntaxEditorTextChange.editshas been renamed toreplacements.SyntaxEditorTextChange.revisionhas been renamed totextRevision.SyntaxEditorModel.latestChangehas been renamed tolatestTextChange.SyntaxEditorTextChange.Kind.replacementhas been renamed towholeDocumentReplacement.- Platform color and font aliases now live under
SyntaxEditorThemeasSyntaxEditorTheme.ColorandSyntaxEditorTheme.Font. SyntaxEditorMenu.makeEditorMenu()andSyntaxEditorMenu.makeEditorMenuItem()have been replaced bySyntaxEditorMenu.makeMenu().SyntaxEditorMenu.insertEditorMenu(into:)andSyntaxEditorMenu.insertEditorMenuItem(into:)have been replaced bySyntaxEditorMenu.insert(into:).SyntaxLanguage.named(_:)has been replaced bySyntaxLanguage.init?(identifier:).SyntaxLanguage.allhas been removed. UseSyntaxLanguage.allCases.
These notes apply when upgrading from v0.11.x or earlier to v0.12.0.
SyntaxEditorColorThemehas been renamed toSyntaxEditorTheme.SyntaxEditorModel.colorThemeand thecolorTheme:initializer argument have been renamed tothemeandtheme:.- Custom
SyntaxEditorThemevalues must include afont. Themes now own editor font size; the editor no longer falls back to a package-level default point size.
These notes apply when upgrading from v0.10.x or earlier to v0.11.0.
SyntaxEditorDocumentandSyntaxEditorConfigurationhave been removed. Create and own a singleSyntaxEditorModelfor text, selection, language, editability, wrapping, theme, background drawing, and font-size state.SyntaxLanguage.plainTexthas been added. Update exhaustive switches overSyntaxLanguageto handle plain text, and use.plainTextfor editors that should not run syntax highlighting or code-aware editing transforms.textSnapshot()has been removed. Read, write, and observemodel.textdirectly. Usemodel.replaceText(_:selectedRange:)when replacement and selection should be updated together.- Replace
SyntaxEditor(document:configuration:)withSyntaxEditor(model). - Replace
SyntaxEditorView(document:configuration:)andSyntaxEditorViewController(document:configuration:)withSyntaxEditorView(model:)andSyntaxEditorViewController(model:). SyntaxEditorDocumentChangehas been renamed toSyntaxEditorTextChange. Usechange.kind == .incrementalorchange.kind == .wholeDocumentReplacementinstead ofisWholeDocumentReplacement.- UIKit and AppKit
text,selectedRange, andisEditableproperties remain available and now proxy to the view'smodel. - On macOS,
SyntaxEditorViewno longer exposes the underlying editor asNSTextView. The editor surface is implemented directly with TextKit 2, matching the iOS architecture. UseSyntaxEditorView.text,SyntaxEditorView.selectedRange,SyntaxEditorView.isEditable, andSyntaxEditorView.modelinstead of reaching throughtextView. - This is a breaking macOS API change: there is no replacement public
NSTextViewaccessor. Code that previously customizededitorView.textViewshould move editor state toSyntaxEditorModelor drive the editor through the publicSyntaxEditorViewproperties above. SyntaxEditorViewController.textViewis no longer public on macOS. Access the editor throughSyntaxEditorViewController.editorViewandmodel.
These notes apply when upgrading from v0.9.x or earlier to v0.10.0.
SyntaxEditorView.fonthas been removed from the public iOS API. UseSyntaxEditorModel.fontSizeDeltaor the font-size command methods to adjust editor text size.
These notes apply when upgrading from v0.7.x or earlier to v0.8.0.
SyntaxEditorColorTheme.xcodehas been removed. UseSyntaxEditorColorTheme.default, shorthand.default, orSyntaxEditorColorTheme.preset(_:)instead.SyntaxEditorColorTheme.idis now aStringinstead of aUUID. If your app stores or compares theme IDs, migrate those values to strings.
These notes apply when upgrading from v0.6.x or earlier to v0.7.0.
SyntaxEditorModelhas been replaced by separateSyntaxEditorDocumentandSyntaxEditorConfigurationobjects.- Store editor text in
SyntaxEditorDocument. Read the current text withtextSnapshot()and replace it withreplaceText(_:selectedRange:). - Store editor settings in
SyntaxEditorConfiguration:language,isEditable,lineWrappingEnabled, andcolorTheme. - Replace
SyntaxEditor(model:)withSyntaxEditor(document:configuration:).SyntaxEditor()is also available when the default document and configuration are enough. - Replace
SyntaxEditorView(model:)andSyntaxEditorViewController(model:)withSyntaxEditorView(document:configuration:)andSyntaxEditorViewController(document:configuration:). - If your app observed
SyntaxEditorModel, observeSyntaxEditorDocumentfor text changes andSyntaxEditorConfigurationfor configuration changes.SyntaxEditorDocumentexposestextRevisionandlatestTextChangefor tracking committed text changes. SyntaxEditorModeland the model-based initializers have been removed without a compatibility shim.
These notes apply when upgrading from v0.4.x or earlier to v0.5.0.
- Starting with
v0.5.0, non-UI implementation has moved into the internalSyntaxEditorCoretarget.SyntaxEditorCoreis not a public package product; clients should keep importingSyntaxEditorUIonly. - In
v0.5.0,SyntaxEditorModel,SyntaxLanguage, and related non-UI APIs remained available fromSyntaxEditorUIvia module re-export.SyntaxEditorModelwas removed inv0.7.0; see thev0.7.0notes above. SyntaxLanguageis now a concrete enum of supported languages. UseSyntaxLanguage.javascriptor shorthand.javascriptinstead ofBuiltinSyntaxLanguages.javascript.BuiltinSyntaxLanguageshas been removed without a compatibility shim.- Custom
SyntaxLanguageconformers are no longer supported.SyntaxLanguage.TreeSitterSupport, custom query directories, and custom highlight cache keys are no longer public API. - HTML embedded JavaScript/CSS highlighting remains supported through
SyntaxLanguage.html. - Up to
v0.4.xon iOS,SyntaxEditorViewembedded aUITextViewthat was exposed throughSyntaxEditorView.textViewandSyntaxEditorViewController.textView. - Starting with
v0.5.0on iOS,SyntaxEditorViewis the single native text input and scroll view. UseSyntaxEditorView/SyntaxEditorViewController.editorViewdirectly for text, selection, editability, wrapping, and scrolling.