A lightweight, high-performance rich-text library for all Apple platforms β UIKit, AppKit, and SwiftUI (including watchOS).
Note: This fork is reimplemented in Swift 6.0 with strict concurrency. Version 2.0 uses renamed public APIs such as
TextLabelView,TextLabel, andTextLabel.Attachment.
- β‘οΈ High performance text layout and rendering via CoreText
- π Native view embedding via attachments
- π Clickable links support
- βοΈ Text selection with copy/paste
- π One selection across several labels, such as the cells of a table
- π¨ Custom per-line drawing callbacks
- π Auto layout integration (experimental)
- π₯οΈ SwiftUI support on all platforms, including watchOS
Litext supports iOS 15, macOS 12, Mac Catalyst 15, tvOS 15, visionOS 1 and watchOS 8, the oldest systems current toolchains deploy to. It requires Swift 6.2 (Xcode 26) or later.
| Platform | Minimum Version | TextLabelView (UIKit/AppKit) | TextLabel (SwiftUI) |
|---|---|---|---|
| iOS | 15.0+ | β | β |
| macOS | 12.0+ | β | β |
| tvOS | 15.0+ | β | β |
| visionOS | 1.0+ | β | β |
| Mac Catalyst | 15.0+ | β | β |
| watchOS | 8.0+ | β | β |
Add Litext as a dependency in your Package.swift file:
dependencies: [
.package(url: "https://github.com/Helixform/Litext.git", branch: "main")
]Or in Xcode: File β Add Package Dependencies and enter the repository URL.
import Litext
let label = TextLabelView()
view.addSubview(label)
let attributedString = NSMutableAttributedString(
string: "Hello, Litext!",
attributes: [
.font: PlatformFont.systemFont(ofSize: 16),
.foregroundColor: PlatformColor.label
]
)
label.attributedText = attributedStringTextLabel works on all platforms, including watchOS:
import Litext
import SwiftUI
struct ContentView: View {
var body: some View {
TextLabel("Hello, Litext!")
.selectable()
.onTapLink { url in
UIApplication.shared.open(url)
}
}
}You can also initialise with an NSAttributedString or AttributedString:
TextLabel(attributedString: myNSAttributedString)
TextLabel(attributedString: myAttributedString)let mutable = NSMutableAttributedString(string: "Visit GitHub")
mutable.addAttribute(.link, value: URL(string: "https://github.com")!, range: NSRange(location: 6, length: 6))
label.attributedText = mutable
// UIKit/AppKit β implement TextLabelViewDelegate
label.delegate = self
func textLabelView(
_ textLabelView: TextLabelView,
didTapHighlightRegion region: TextLabel.HighlightRegion,
at location: CGPoint
) {
if let url = region.linkURL {
UIApplication.shared.open(url)
}
}
// SwiftUI β use the modifier
TextLabel(attributedString: mutable)
.onTapLink { url in
UIApplication.shared.open(url)
}// Enable selection
label.isSelectable = true
label.selectionBackgroundColor = UIColor.systemBlue.withAlphaComponent(0.2)
// Access selected text
let text = label.selectedPlainText()
let attributed = label.selectedAttributedText()
// Programmatic selection
label.selectionRange = NSRange(location: 0, length: 5)
label.selectAll()
label.clearSelection()
// SwiftUI
TextLabel("Some selectable text")
.selectable()Labels that share a TextSelectionGroup share one selection, so a selection can run from one label into the next, for example across the cells of a table. Copy, Look Up, Translate and Share act on all of it.
let group = TextSelectionGroup()
// The order is the reading order: list a table row by row.
group.labels = cells
// Put a tab between cells of a row and a line break between rows.
group.separator = { previous, next in row(of: previous) == row(of: next) ? "\t" : "\n" }
group.delegate = self
// TextSelectionGroupDelegate
func textSelectionGroupDidChangeSelection(_ group: TextSelectionGroup) {
print(group.selectedPlainText() ?? "")
}
// Add commands to the menu (iOS 16, Mac Catalyst 16 or later).
func textSelectionGroup(
_ group: TextSelectionGroup,
editMenuForSuggestedActions suggestedActions: [UIMenuElement],
) -> UIMenu? {
UIMenu(children: suggestedActions + [copyAsMarkdownAction])
}The group holds its labels weakly; keep the labels alive as usual. group.selectedSegments lists the selected range in each label.
Use TextLabel.Attachment to embed any view inline in the text:
// UIKit / AppKit
let attachment = TextLabel.Attachment()
attachment.view = myCustomView // UIView or NSView
attachment.size = myCustomView.intrinsicContentSize
// watchOS (SwiftUI view instead)
let attachment = TextLabel.Attachment()
attachment.swiftUIView = AnyView(MyCustomView())
attachment.size = CGSize(width: 100, height: 50)
// Insert attachment into attributed string
let attachmentString = attachment.attributedString()let drawingAction = TextLabel.LineDrawingAction { context, line, origin in
// Custom drawing for each line
context.setStrokeColor(UIColor.red.cgColor)
context.move(to: CGPoint(x: origin.x, y: origin.y - 2))
context.addLine(to: CGPoint(x: origin.x + 100, y: origin.y - 2))
context.strokePath()
}
attributedString.addAttribute(
.litextLineDrawingAction,
value: drawingAction,
range: fullRange
)On watchOS, TextLabelView (the UIView/NSView subclass) is not available. Use TextLabel instead β it renders via an off-screen CGContext and displays the result as a SwiftUI Image.
import Litext
import SwiftUI
struct WatchContentView: View {
var body: some View {
TextLabel(attributedString: styledText)
}
var styledText: NSAttributedString {
let s = NSMutableAttributedString(string: "Hello from Watch!")
s.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: 14), range: NSRange(location: 0, length: 17))
return s
}
}For inline attachments on watchOS, provide a SwiftUI view via swiftUIView instead of view.
This project is licensed under the MIT License β see the LICENSE file for details.


