txt-drag-drop — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited txt-drag-drop (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 covers drag and drop inside text editors — both the iOS-specific UITextDraggable/UITextDroppable protocols (and the delegates that customize them) and the macOS NSDraggingSource/NSDraggingDestination architecture. Before relying on a specific delegate signature, fetch via Sosumi (sosumi.ai/documentation/uikit/uitextdraggable) — the drop proposal types and same-view operation flags have shifted across iOS versions.
The shared substrate is NSItemProvider type negotiation, identical to the modern paste path. Most drop bugs are gesture priorities (drag wins over selection) or a missing protocol on iPhone (text drag is off by default there). On macOS the architecture is genuinely different — the iOS-style delegates do not exist.
UITextView and UITextField adopt UITextDraggable and UITextDroppable automatically on iPad. The user lifts a selection with a long-press, the system makes drag items from the selected text, and drops insert at the caret position. Same-view drops behave as moves (the source range is removed); cross-view or cross-app drops behave as copies.
iPhone is the exception: text drag is disabled by default. The drop side is enabled. To turn drag on:
textView.textDragInteraction?.isEnabled = trueIf users on iPhone can't initiate a drag, this is almost always the cause. The interaction is installed but switched off.
Set textView.textDragDelegate = self. All methods are optional; you implement only the seams you care about.
func textDraggableView(_ view: UIView & UITextDraggable,
itemsForDrag req: UITextDragRequest) -> [UIDragItem] {
let plain = req.suggestedItems.first?.localObject as? String ?? ""
let textItem = UIDragItem(itemProvider: NSItemProvider(object: plain as NSString))
// Add an app-specific representation
let custom = encodeRichFormat(for: req.dragRange)
let customItem = UIDragItem(itemProvider: NSItemProvider(
item: custom as NSData,
typeIdentifier: "com.example.myapp.richtext"
))
return [textItem, customItem]
}Returning an empty array disables drag for that selection. To disable globally, switch off textDragInteraction.isEnabled instead — it's clearer and avoids running the delegate per gesture.
To strip text colors from the drag preview (useful when the document has high-contrast styling that looks bad lifted on the screen background):
textView.textDragOptions = .stripTextColorFromPreviewsLifecycle hooks:
func textDraggableView(_ v: UIView & UITextDraggable,
dragSessionWillBegin s: UIDragSession) {
// pause autosave, hide cursor, etc.
}
func textDraggableView(_ v: UIView & UITextDraggable,
dragSessionDidEnd s: UIDragSession) {
// resume normal state
}The drop side returns a proposal describing what the drop will do at this position:
func textDroppableView(_ v: UIView & UITextDroppable,
proposalForDrop drop: UITextDropRequest) -> UITextDropProposal {
let proposal = drop.isSameView
? UITextDropProposal(dropAction: .insert) // moves within view
: UITextDropProposal(dropAction: .insert)
proposal.useFastSameViewOperations = true
return proposal
}The actions are .insert (insert at drop position, the usual case), .replaceSelection (replace whatever is currently selected), and .replaceAll (replace the whole document — used for "open this file" style drops). useFastSameViewOperations lets the system optimize the move case by avoiding a full round-trip through serialization.
willPerformDrop runs just before the drop applies, useful for validation, undo grouping, or telemetry:
func textDroppableView(_ v: UIView & UITextDroppable,
willPerformDrop drop: UITextDropRequest) {
// last chance to log or fail
}Default drag previews work for single-line selections. Multi-line text needs UITextDragPreviewRenderer, which produces previews that follow the actual line geometry:
let renderer = UITextDragPreviewRenderer(
layoutManager: textView.layoutManager,
range: textView.selectedRange
)
renderer.adjust(firstLineRect: &firstLineRect,
bodyRect: &bodyRect,
lastLineRect: &lastLineRect,
textOrigin: origin)The renderer doesn't return a UITargetedDragPreview directly; you compose one using the rects it adjusts. For the lifting preview, return a UITargetedDragPreview from dragPreviewForLiftingItem; for the drop animation, return one from previewForDroppingAllItemsWithDefault.
Read-only text views reject drops by default — same flag controls both editing and dropping. Override to accept drops on a non-editable view:
func textDroppableView(_ v: UIView & UITextDroppable,
willBecomeEditableForDrop drop: UITextDropRequest) -> UITextDropEditability {
.temporary // editable for this drop only
// .yes — permanently editable
// .no — reject drop (default)
}.temporary is the typical answer when the document is conceptually read-only but specific commands (like "drop here to import") should write through.
UITextDraggable and UITextDroppable are NOT auto-adopted by custom views. Only UITextField and UITextView get them. A custom view needs the general drag/drop interactions and translates positions into UITextPosition values manually.
final class CustomEditor: UIView, UITextInput {
override init(frame: CGRect) {
super.init(frame: frame)
addInteraction(UIDragInteraction(delegate: self))
addInteraction(UIDropInteraction(delegate: self))
}
}
extension CustomEditor: UIDragInteractionDelegate {
func dragInteraction(_ i: UIDragInteraction,
itemsForBeginning session: any UIDragSession) -> [UIDragItem] {
guard let s = textInSelectedRange() else { return [] }
return [UIDragItem(itemProvider: NSItemProvider(object: s as NSString))]
}
}
extension CustomEditor: UIDropInteractionDelegate {
func dropInteraction(_ i: UIDropInteraction,
performDrop session: any UIDropSession) {
let point = session.location(in: self)
guard let pos = closestPosition(to: point) else { return }
for item in session.items {
_ = item.itemProvider.loadObject(ofClass: NSString.self) { obj, _ in
guard let s = obj as? String else { return }
Task { @MainActor in self.insert(s, at: pos) }
}
}
}
}The drop point is in the view's coordinate space, and closestPosition(to:) is the same UITextInput method system gestures use — see txt-uitextinput for the position arithmetic that backs it.
AppKit has no analog to UITextDragDelegate. Drag is initiated by NSDraggingSource, drops are received by NSDraggingDestination — both protocols are adopted by NSView, so any text view inherits a baseline.
final class CustomTextView: NSTextView {
override var acceptableDragTypes: [NSPasteboard.PasteboardType] {
var types = super.acceptableDragTypes
types.append(.init("com.example.myapp.richtext"))
return types
}
override func performDragOperation(_ info: NSDraggingInfo) -> Bool {
let pb = info.draggingPasteboard
if let data = pb.data(forType: .init("com.example.myapp.richtext")) {
insertCustomContent(data)
return true
}
return super.performDragOperation(info)
}
}Two macOS-specific gotchas. First, file drops onto NSTextView only work if both isRichText and importsGraphics are true; either alone won't do it. Second, when the user drags onto an NSTextField while it's being edited, the drop hits the shared field editor (an NSTextView instance), not the text field itself. To intercept, supply a custom field editor:
func windowWillReturnFieldEditor(_ sender: NSWindow, to client: Any?) -> Any? {
client is MyTextField ? customFieldEditor : nil
}textDragInteraction.isEnabled defaults to false on iPhone. Set it to true if you want users to lift selections.willBecomeEditableForDrop returning .temporary to opt in for specific drops while keeping the document read-only otherwise.UITextView and UITextField get the text-specific protocols. A custom view needs UIDragInteraction and UIDropInteraction added by hand and translates drop points to UITextPosition itself.UITextDragPreviewRenderer to follow per-line geometry; otherwise multi-line lifts look glued together.textDragInteraction.isEnabled = false instead.drop.isSameView if behavior depends on the source.isRichText and importsGraphics must be true for file drops to land on macOS text views.windowWillReturnFieldEditor(_:to:) to intercept.txt-pasteboard — clipboard operations and the shared NSItemProvider type-negotiation patterntxt-uitextinput — UITextInput implementation for custom views, including closestPosition(to:) and position arithmetic used by drop handlerstxt-attachments — when dropped images become inline NSTextAttachment contenttxt-selection-menus — gesture coordination, useful when drag conflicts with selection or long-press~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.