txt-view-picker — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited txt-view-picker (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Authored against iOS 26.x / Swift 6.x / Xcode 26.x.
This skill is a capability comparison across the Apple text views. The capability matrix below is a starting point; before committing a view choice, open the actual feature requirements list and confirm each line item against the matrix — view selection mistakes propagate into wrapper code, performance work, and Writing Tools integration that are expensive to undo. If you are deciding based on a single requirement, you have not surveyed the full requirement set yet.
A view class isn't picked from the framework alone. SwiftUI Text displays attributed strings but ignores paragraphStyle. SwiftUI TextField accepts a vertical axis since iOS 16 and handles many cases that previously required UITextView. SwiftUI TextEditor gained real rich-text editing on iOS 26 but still cannot do inline images, lists, or TextKit access. The right answer depends on the combination of features you need, the deployment floor, and whether the view will be wrapped, embedded, or composed.
Five views cover almost every case:
String, LocalizedStringKey, and a defined subset of AttributedString attributes. No editing, no cursor.axis: .vertical since iOS 16. Plain String binding, with format: for typed values.String on iOS 14-25; AttributedString rich-text editing on iOS 26+.UILabel / NSTextField exist for the cases where a view doesn't wrap into the SwiftUI hierarchy. They're rarely the answer in a SwiftUI app — Text and TextField cover the same ground.
If the text is read-only, the choice collapses fast. SwiftUI Text is the right answer for nearly all read-only display, including styled AttributedString, inline Markdown literals, and dynamic text composition via the + operator. The exceptions are narrow:
Text.textSelection(.enabled) only supports select-all on iOS; range selection works on macOS but not iOS.UITextView or NSTextView configured non-editable.Text only renders inline Markdown; block structure ends up in presentationIntent and is silently ignored. Either preprocess into a SwiftUI view tree or render via TextKit.For editing, the question is what kind of input.
If the binding can stay as String, SwiftUI is usually the right call. TextField covers single-line and (since iOS 16) modest multi-line growth. TextEditor covers always-multi-line editing.
TextField(axis: .vertical) is underused — it grows to fit content, accepts lineLimit(2...8) for bounded growth, supports a placeholder via the prompt parameter, and behaves correctly inside forms and lists. For chat composers and comment fields it is almost always the right answer over TextEditor or a wrapped UITextView.
TextEditor is the right choice when the editor must always be multi-line, when there is no placeholder requirement, or when you need iOS 26 rich-text editing. On iOS 25 and earlier, TextEditor is plain-text only and has no prompt — overlay a Text view manually if a placeholder is needed, or use TextField(axis: .vertical).
The iOS 26 rich-text variant (TextEditor with AttributedString binding) handles bold/italic/underline, foreground/background colors, alignment, line height, and writing direction. Genmoji insertion works. What it can't do — inline images, lists, tables, exclusion paths, TextKit access, custom layout — is a real ceiling, so check requirements before picking it for anything beyond simple rich text.
The boundary at which SwiftUI stops working is well-defined. Drop to UITextView (or NSTextView) when any of these are true:
textStorage, layoutManager (TK1), or textLayoutManager (TK2).NSTextAttachment views.NSTextTable.UITextInputTraits.Once any of these is on the requirement list, SwiftUI text views become a poor fit and a UIViewRepresentable wrapping UITextView (or NSViewRepresentable wrapping NSTextView inside an NSScrollView) is the path. The wrapping mechanics are non-trivial — that is its own skill.
UITextView and NSTextView are not interchangeable. UITextView is a UIScrollView, has UITextInteraction for modular gestures, and gained UITextItem interactions in iOS 17. NSTextView lives inside an NSScrollView, owns text tables, ruler, font panel, Services menu, and NSText's RTF I/O. Cross-platform code typically wraps each in its own representable.
A few cases come up often enough to spell out:
TextField(axis: .vertical) first. Drop to a wrapped UITextView only if you need attributed editing, attachments, or TextKit features. Don't reach for TextEditor here — it always takes its full proposed height and has no placeholder.TextEditor with AttributedString first. Drop to a wrapped UITextView if you need attachments, lists, or TextKit access.UITextView. Plain TextEditor doesn't accept AttributedString on those versions.UITextView / NSTextView. TextKit 1 if you need temporary attributes (proven, fast) or glyph metrics; TextKit 2 if viewport performance on huge files is critical. Neither is "legacy" or "modern" — they solve different problems.Text with an AttributedString or inline Markdown literal. UILabel only when you can't be in SwiftUI.TextField. Use format: for typed values (currency, integers, dates).Text with AttributedString(markdown:). If block-level (headings, lists, quotes) — TextKit-backed view or a third-party SwiftUI Markdown renderer.NSTextView on macOS. UIKit has no equivalent for text tables or rulers.Read-only styled text in SwiftUI:
// Inline Markdown literal — interpreted at compile time
Text("Visit **[example.com](https://example.com)** today.")
// Runtime AttributedString
var attr = AttributedString("Important note")
attr.foregroundColor = .red
attr.font = .body.bold()
Text(attr).textSelection(.enabled)Single-line input with a typed value:
@State private var price: Double = 0
TextField("Price", value: $price, format: .currency(code: "USD"))
.textFieldStyle(.roundedBorder)
.keyboardType(.decimalPad)
.submitLabel(.done)Vertical-axis chat composer:
TextField("Compose…", text: $body, axis: .vertical)
.lineLimit(2...8)iOS 26 rich-text editing:
@State private var text = AttributedString("Edit this text")
var body: some View {
TextEditor(text: $text)
}Wrapped UITextView for rich editing on older iOS or when TextKit access is needed:
struct RichTextEditor: UIViewRepresentable {
@Binding var attributedText: NSAttributedString
func makeUIView(context: Context) -> UITextView {
let tv = UITextView()
tv.delegate = context.coordinator
tv.backgroundColor = .clear // let SwiftUI background show
return tv
}
func updateUIView(_ tv: UITextView, context: Context) {
guard tv.attributedText != attributedText else { return } // prevent loop
tv.attributedText = attributedText
}
func makeCoordinator() -> Coordinator { Coordinator(self) }
final class Coordinator: NSObject, UITextViewDelegate {
var parent: RichTextEditor
init(_ p: RichTextEditor) { parent = p }
func textViewDidChange(_ tv: UITextView) {
parent.attributedText = tv.attributedText
}
}
}The wrapper above is a starting sketch. Real-world wrappers have to handle focus, sizing, cursor preservation, and update-loop guards correctly — see the wrap-textview skill.
TextField covers the chat-composer case natively since iOS 16, including placeholder, line-limit growth, and form integration. A wrapped UITextView is the right answer only when attributed editing, attachments, or TextKit access is on the requirement list. Defaulting to a wrapper to "be safe" buys complexity you'll pay for in update-loop bugs and focus management.presentationIntent and silently dropped from the rendered output. The text appears unformatted. If block-level rendering matters, use TextKit, a third-party SwiftUI Markdown view, or render the parsed structure into a SwiftUI view tree manually.textViewDidChange, which writes back to the binding, which calls updateUIView, which sets attributedText again. Infinite loop, or at minimum cursor jumps every keystroke. Guard with guard tv.attributedText != attributedText else { return }.UITextView instead.systemBackground color paints over any SwiftUI background, list separator styling, or material effect behind the wrapped view. Always clear the background and let SwiftUI's chrome show through.TextEditor has no prompt parameter on any iOS version. Either overlay a Text view manually (showing/hiding based on the binding being empty) or switch to TextField(axis: .vertical), which has prompt and similar growth behavior.UITextView remains the safer choice. Treat the iOS 26 path as additive, not a replacement.references/reference.md — capability matrix and platform-by-platform reference, loaded only when neededreferences/examples.md — usage-oriented examples, loaded only when needed/skill txt-wrap-textview — wrapping UITextView / NSTextView in SwiftUI/skill txt-swiftui-interop — which AttributedString attributes survive the SwiftUI/TextKit boundary/skill txt-swiftui-texteditor — iOS 26 SwiftUI TextEditor rich-text APIs/skill txt-textkit-choice — TextKit 1 vs TextKit 2 decision/skill txt-appkit-vs-uikit — NSTextView vs UITextView capability comparison~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.