txt-snippets — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited txt-snippets (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 catalog of working snippets for the small, recurring "how do I add X to a text view" tasks. Each recipe is intended to drop into a project with minimal adjustment. The patterns here are starting points, not final code — verify the integration in the actual project before claiming the recipe matches what's needed, and confirm any API used here against current Apple docs via Sosumi if the snippet is a few releases old.
The five most-asked recipes are inlined below. The full catalog of twenty recipes lives in references/recipes.md, grouped by topic.
UITextView has no built-in placeholder. Subclass and draw the placeholder when the text is empty, then invalidate display whenever text or placeholder changes.
class PlaceholderTextView: UITextView {
var placeholder: String = "" { didSet { setNeedsDisplay() } }
override var text: String! { didSet { setNeedsDisplay() } }
override func draw(_ rect: CGRect) {
super.draw(rect)
guard text.isEmpty else { return }
let inset = textContainerInset
let pad = textContainer.lineFragmentPadding
let drawRect = CGRect(
x: inset.left + pad,
y: inset.top,
width: bounds.width - inset.left - inset.right - 2 * pad,
height: bounds.height - inset.top - inset.bottom
)
placeholder.draw(in: drawRect, withAttributes: [
.font: font ?? .systemFont(ofSize: 17),
.foregroundColor: UIColor.placeholderText
])
}
}Hook the host's textViewDidChange(_:) (or NSText.didChangeNotification) to call setNeedsDisplay() on every keystroke so the placeholder appears as soon as the text becomes empty again.
Enforce a limit in shouldChangeTextIn:replacementText:. Compute the would-be length first; reject the edit if it exceeds the cap.
func textView(_ textView: UITextView,
shouldChangeTextIn range: NSRange,
replacementText text: String) -> Bool {
let current = (textView.text as NSString).length
let newLength = current - range.length + (text as NSString).length
return newLength <= 280
}Use NSString length for the math — Swift String.count returns grapheme clusters, which diverges from NSRange units on emoji and combining marks. For word limits, count text.split(separator: " ", omittingEmptySubsequences: true).count against the projected text after the edit.
A UITextView reports a non-trivial intrinsicContentSize only when scrolling is disabled. Disable scrolling, let Auto Layout drive the height, and re-enable scrolling once a maximum height is reached.
textView.isScrollEnabled = false // intrinsicContentSize now drives layout
textView.heightAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
func textViewDidChange(_ textView: UITextView) {
let fits = textView.sizeThatFits(
CGSize(width: textView.bounds.width, height: .greatestFiniteMagnitude)
)
textView.isScrollEnabled = fits.height > 200
}If the view is inside a stack or scroll view, ensure the parent honors the changed intrinsic size — a sibling with lowest content-hugging priority can absorb the growth instead of the editor.
Mutating attributedText to highlight matches invalidates layout and disturbs typing attributes. Use the layout manager's temporary attributes on TextKit 1 — they live above the storage and don't enter the editing lifecycle.
func highlight(_ query: String, in textView: UITextView) {
guard let lm = textView.layoutManager, let text = textView.text else { return }
let full = NSRange(location: 0, length: (text as NSString).length)
lm.removeTemporaryAttribute(.backgroundColor, forCharacterRange: full)
var search = text.startIndex..<text.endIndex
while let r = text.range(of: query, options: .caseInsensitive, range: search) {
lm.addTemporaryAttribute(.backgroundColor,
value: UIColor.systemYellow,
forCharacterRange: NSRange(r, in: text))
search = r.upperBound..<text.endIndex
}
}Note: accessing layoutManager flips a TextKit 2 UITextView to TextKit 1 permanently. On TextKit 2, use setRenderingAttributes(_:for:) on the layout manager instead — same effect, no fallback. See txt-fallback-triggers.
Built-in detection in UITextView requires a non-editable view. The data-detector property is silently ignored when editing is enabled.
textView.isEditable = false
textView.dataDetectorTypes = [.link, .phoneNumber, .address, .calendarEvent]For editable text views, run NSDataDetector over the content and apply .link attributes to the matched ranges manually. See txt-detectors-tagger for the detector setup.
The complete catalog lives in references/recipes.md:
| # | Recipe |
|---|---|
| 1 | Background color behind a paragraph |
| 2 | Line numbers in a text view |
| 3 | Character or word limit on input |
| 4 | Text wrapping around an image |
| 5 | Clickable links (read-only) |
| 6 | Clickable links (editable) |
| 7 | Placeholder text in UITextView |
| 8 | Auto-growing text view |
| 9 | Highlight search results |
| 10 | Strikethrough text |
| 11 | Letter spacing (tracking and kern) |
| 12 | Different line heights per paragraph |
| 13 | First-line indent on paragraphs |
| 14 | Bullet or numbered lists |
| 15 | Read-only styled text |
| 16 | Auto-detect data (phones, URLs, dates) |
| 17 | Custom cursor color |
| 18 | Disable text selection |
| 19 | Programmatically scroll to range |
| 20 | Get current line number |
| 21 | Strip formatting on paste |
| 22 | Convert AttributedString ↔ NSAttributedString |
| 23 | Export NSAttributedString to RTF / HTML |
| 24 | Suppress autocorrect / smart punctuation |
| 25 | Auto-resizing UITextView in SwiftUI |
| 26 | Smart pairs and auto-indent (cursor-stable) |
| 27 | Tree-sitter concurrent reparse |
| 28 | Modern keyboard avoidance with keyboardLayoutGuide |
Recipes show UIKit code by default. Recipes that touch only NSTextStorage, NSAttributedString, or NSParagraphStyle work on AppKit unchanged. UIKit-specific surfaces (UITextView, content-size category, dataDetectorTypes) need the AppKit equivalent — see the platform-translation table at the top of references/recipes.md.
isEditable = false views; for editable views, run NSDataDetector and apply attributes yourself.(text as NSString).length or NSRange(swiftRange, in: text).UIView.noIntrinsicMetric for the height while scrolling is enabled. Disable scrolling first, then conditionally re-enable past a maximum height.draw(_:). Without setNeedsDisplay(), the previous placeholder lingers until the next layout pass./skill txt-view-picker — picking a text view in the first place/skill txt-line-breaking — paragraph style internals (line height, spacing, tab stops)/skill txt-attribute-keys — formatting attribute catalog/skill txt-measurement — sizing text and views to fit content/skill txt-detectors-tagger — NSDataDetector for editable views/skill txt-fallback-triggers — TextKit 2 → 1 fallback when accessing layoutManager~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.