Text Drag and Drop
Use when customizing drag and drop in Apple text editors, including UITextDraggable or UITextDroppable, drag previews, multi-line selections, iPhone drag enablement, or custom drop handling in UITextInput views. Reach for this when the task is editor drag-and-drop behavior, not pasteboard-only workflows.
Use when customizing drag and drop in Apple text editors, including UITextDraggable or UITextDroppable, drag previews, multi-line selections, iPhone drag enablement, or custom drop handling in UITextInput views. Reach for this when the task is editor drag-and-drop behavior, not pasteboard-only workflows.
Family: Editor Features And Interaction
Use this skill when the main question is how text-specific drag and drop works in Apple text editors, or when customizing drag/drop behavior beyond the defaults.
When to Use
Section titled “When to Use”- Customizing what gets dragged from a text view
- Controlling drop behavior (insert, replace selection, replace all)
- Enabling text drag on iPhone (disabled by default)
- Building drag/drop for a custom UITextInput view
- Handling drops in non-editable text views
- Custom drag previews for multi-line text
Quick Decision
Section titled “Quick Decision”Using UITextView or UITextField? → Drag and drop works automatically (iPad). Configure via delegates. → iPhone: must enable explicitly.
Building a custom UITextInput view? → Text drag/drop protocols are NOT automatically adopted. → Must add UIDragInteraction / UIDropInteraction manually.
Need copy/paste instead of drag/drop? → /skill apple-text-pasteboard
macOS? → Different architecture (NSDraggingSource / NSDraggingDestination)Core Guidance
Section titled “Core Guidance”Default Behavior
Section titled “Default Behavior”UITextView and UITextField conform to UITextDraggable and UITextDroppable automatically.
Drag: User selects text, long-presses the selection to lift it. The system creates drag items with the selected text.
Drop: Text views accept dropped text, inserting at the position under the user’s finger. The caret tracks the drag position.
Move vs copy:
- Same text view → move (dragged text removed from original position)
- Different view or app → copy
iPhone vs iPad
Section titled “iPhone vs iPad”// iPad: drag enabled by default// iPhone: drag DISABLED by defaulttextView.textDragInteraction?.isEnabled = true // Enable on iPhoneUITextDragDelegate
Section titled “UITextDragDelegate”All methods are optional. Set via textView.textDragDelegate = self.
Providing Custom Drag Items
Section titled “Providing Custom Drag Items”func textDraggableView(_ textDraggableView: UIView & UITextDraggable, itemsForDrag dragRequest: UITextDragRequest) -> [UIDragItem] { // Return custom items (e.g., add image alongside text) let text = dragRequest.suggestedItems.first?.localObject as? String ?? "" let textItem = UIDragItem(itemProvider: NSItemProvider(object: text as NSString))
// Add a custom representation let customData = encodeRichFormat(for: dragRequest.dragRange) let customItem = UIDragItem(itemProvider: NSItemProvider( item: customData as NSData, typeIdentifier: "com.myapp.richtext" ))
return [textItem, customItem]}Disabling Drag
Section titled “Disabling Drag”// Option A: Return empty arrayfunc textDraggableView(_ textDraggableView: UIView & UITextDraggable, itemsForDrag dragRequest: UITextDragRequest) -> [UIDragItem] { return [] // Disables drag}
// Option B: Disable the interactiontextView.textDragInteraction?.isEnabled = falseCustom Drag Preview
Section titled “Custom Drag Preview”func textDraggableView(_ textDraggableView: UIView & UITextDraggable, dragPreviewForLiftingItem item: UIDragItem, session: UIDragSession) -> UITargetedDragPreview? { // Return nil for default preview // Return custom UITargetedDragPreview for custom appearance return nil}UITextDragPreviewRenderer
Section titled “UITextDragPreviewRenderer”Text-aware preview rendering that understands multi-line text geometry:
// Create a renderer from layout manager and rangelet renderer = UITextDragPreviewRenderer( layoutManager: textView.layoutManager, range: selectedRange)
// Adjust the preview rectanglesrenderer.adjust(firstLineRect: &firstRect, bodyRect: &bodyRect, lastLineRect: &lastRect, textOrigin: origin)
// The renderer provides proper multi-line drag previews// that follow the text's line geometryStrip Color from Previews
Section titled “Strip Color from Previews”textView.textDragOptions = .stripTextColorFromPreviews// Renders drag preview in uniform color instead of preserving text colorsLifecycle Hooks
Section titled “Lifecycle Hooks”func textDraggableView(_ textDraggableView: UIView & UITextDraggable, dragSessionWillBegin session: UIDragSession) { // Drag is starting — pause syncing, show visual feedback}
func textDraggableView(_ textDraggableView: UIView & UITextDraggable, dragSessionDidEnd session: UIDragSession) { // Drag ended — resume normal operations}UITextDropDelegate
Section titled “UITextDropDelegate”All methods are optional. Set via textView.textDropDelegate = self.
Controlling Drop Behavior
Section titled “Controlling Drop Behavior”func textDroppableView(_ textDroppableView: UIView & UITextDroppable, proposalForDrop drop: UITextDropRequest) -> UITextDropProposal { // Check if this is a same-view drop (move vs copy) if drop.isSameView { return UITextDropProposal(dropAction: .insert) }
// Accept external drops as insert at drop position return UITextDropProposal(dropAction: .insert)}UITextDropProposal Actions
Section titled “UITextDropProposal Actions”| Action | Behavior |
|---|---|
.insert | Insert at drop position (default) |
.replaceSelection | Replace the current text selection |
.replaceAll | Replace all text in the view |
// Replace selection on droplet proposal = UITextDropProposal(dropAction: .replaceSelection)
// Optimize same-view operationsproposal.useFastSameViewOperations = trueHandling the Drop
Section titled “Handling the Drop”func textDroppableView(_ textDroppableView: UIView & UITextDroppable, willPerformDrop drop: UITextDropRequest) { // Called just before the drop executes // Use for validation, logging, or pre-processing}Non-Editable Text Views
Section titled “Non-Editable Text Views”By default, non-editable text views reject drops. Override with:
func textDroppableView(_ textDroppableView: UIView & UITextDroppable, willBecomeEditableForDrop drop: UITextDropRequest) -> UITextDropEditability { return .temporary // Become editable just for this drop, then revert // .no — reject the drop (default for non-editable) // .yes — become permanently editable}Custom Drop Preview
Section titled “Custom Drop Preview”func textDroppableView(_ textDroppableView: UIView & UITextDroppable, previewForDroppingAllItemsWithDefault defaultPreview: UITargetedDragPreview) -> UITargetedDragPreview? { // Return nil for default animation // Return custom preview for custom drop animation return nil}macOS (AppKit)
Section titled “macOS (AppKit)”macOS text drag/drop uses a completely different architecture — no UITextDragDelegate equivalent.
NSTextView Default Behavior
Section titled “NSTextView Default Behavior”NSTextView uses NSDraggingSource and NSDraggingDestination (which NSView conforms to). By default:
- Text can be dragged from selections
- Text drops are accepted if the view is editable
- File drops are only accepted if
isRichTextandimportsGraphicsare both enabled
Customizing on macOS
Section titled “Customizing on macOS”class CustomTextView: NSTextView { // Accept additional pasteboard types override var acceptableDragTypes: [NSPasteboard.PasteboardType] { var types = super.acceptableDragTypes types.append(.init("com.myapp.richtext")) return types }
// Handle the drop override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool { let pasteboard = draggingInfo.draggingPasteboard if let customData = pasteboard.data(forType: .init("com.myapp.richtext")) { insertCustomContent(customData) return true } return super.performDragOperation(draggingInfo) }}Field Editor Gotcha
Section titled “Field Editor Gotcha”During NSTextField editing, drops go to the field editor (shared NSTextView), not the NSTextField itself. To intercept:
func windowWillReturnFieldEditor(_ sender: NSWindow, to client: Any?) -> Any? { if client is MyTextField { return myCustomFieldEditor // Subclass NSTextView with custom drop handling } return nil}Custom UITextInput Views
Section titled “Custom UITextInput Views”UITextDraggable and UITextDroppable are NOT automatically adopted by custom UITextInput views. Only UITextField and UITextView get them.
For custom views, use the general drag/drop APIs:
class CustomEditor: UIView, UITextInput { override init(frame: CGRect) { super.init(frame: frame)
// Add general drag/drop interactions let dragInteraction = UIDragInteraction(delegate: self) addInteraction(dragInteraction)
let dropInteraction = UIDropInteraction(delegate: self) addInteraction(dropInteraction) }}
extension CustomEditor: UIDragInteractionDelegate { func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: any UIDragSession) -> [UIDragItem] { guard let selectedText = textInSelectedRange() else { return [] } let provider = NSItemProvider(object: selectedText as NSString) return [UIDragItem(itemProvider: provider)] }}
extension CustomEditor: UIDropInteractionDelegate { func dropInteraction(_ interaction: UIDropInteraction, performDrop session: any UIDropSession) { // Handle the drop using UITextInput methods // Convert drop point to UITextPosition let point = session.location(in: self) guard let position = closestPosition(to: point) else { return } // Insert text at position }}Platform Comparison
Section titled “Platform Comparison”| Feature | iOS (UITextView) | macOS (NSTextView) |
|---|---|---|
| Text drag/drop protocols | UITextDraggable / UITextDroppable | NSDraggingSource / NSDraggingDestination |
| Specialized delegates | UITextDragDelegate / UITextDropDelegate | None (general dragging APIs) |
| Drop proposal system | UITextDropProposal with actions | performDragOperation override |
| Multi-line preview | UITextDragPreviewRenderer | System-provided |
| iPhone drag | Disabled by default | N/A |
| File drops on text | Supported | Only if isRichText + importsGraphics |
| Move vs copy | Automatic (same view = move) | Manual via operation mask |
Common Pitfalls
Section titled “Common Pitfalls”- Drag not working on iPhone —
textDragInteraction?.isEnableddefaults tofalseon iPhone. Must enable explicitly. - Non-editable views rejecting drops — Implement
willBecomeEditableForDropreturning.temporaryto accept drops on read-only views. - Custom UITextInput views have no text drag/drop — Must add UIDragInteraction/UIDropInteraction manually. The text-specific protocols only apply to UITextField and UITextView.
- Attributed text ignored in drag previews — Known issue (rdar://34098227). Provide custom drag items to ensure attributed text is included.
- macOS field editor intercepts drops — Drops during NSTextField editing go to the field editor, not the text field. Provide a custom field editor to intercept.
- Move vs copy confusion — Same-view drops default to move (source text deleted). Cross-view defaults to copy. Check
drop.isSameViewin your proposal. - NSTextView not accepting file drops — Both
isRichTextandimportsGraphicsmust be enabled for file drop acceptance on macOS.
Documentation Scope
Section titled “Documentation Scope”This page documents the apple-text-drag-drop workflow skill. Use it when the job is a guided review, implementation flow, or integration pass instead of a single API lookup.
Related
Section titled “Related”apple-text-pasteboard: Use when handling copy, cut, and paste in Apple text editors, including stripping formatting, sanitizing rich text, custom pasteboard types, pasted attachments, or NSItemProvider bridging. Reach for this when the problem is pasteboard behavior, not general editor interaction.apple-text-interaction: Use when customizing text-editor interactions in UIKit, such as selection behavior, edit menus, link taps, gestures, cursor appearance, or long-press actions. Reach for this when the problem is interaction behavior, not custom text input protocol plumbing.apple-text-input-ref: Use when the user already knows the problem lives in the text input system and needs exact UITextInput, UIKeyInput, NSTextInputClient, marked-text, or selection-UI behavior. Reach for this when implementing or debugging custom text input plumbing, not high-level editor interactions alone.
Full SKILL.md source
---name: apple-text-drag-dropdescription: Use when customizing drag and drop in Apple text editors, including UITextDraggable or UITextDroppable, drag previews, multi-line selections, iPhone drag enablement, or custom drop handling in UITextInput views. Reach for this when the task is editor drag-and-drop behavior, not pasteboard-only workflows.license: MIT---
# Text Drag and Drop
Use this skill when the main question is how text-specific drag and drop works in Apple text editors, or when customizing drag/drop behavior beyond the defaults.
## When to Use
- Customizing what gets dragged from a text view- Controlling drop behavior (insert, replace selection, replace all)- Enabling text drag on iPhone (disabled by default)- Building drag/drop for a custom UITextInput view- Handling drops in non-editable text views- Custom drag previews for multi-line text
## Quick Decision
```Using UITextView or UITextField? → Drag and drop works automatically (iPad). Configure via delegates. → iPhone: must enable explicitly.
Building a custom UITextInput view? → Text drag/drop protocols are NOT automatically adopted. → Must add UIDragInteraction / UIDropInteraction manually.
Need copy/paste instead of drag/drop? → /skill apple-text-pasteboard
macOS? → Different architecture (NSDraggingSource / NSDraggingDestination)```
## Core Guidance
## Default Behavior
UITextView and UITextField conform to `UITextDraggable` and `UITextDroppable` automatically.
**Drag:** User selects text, long-presses the selection to lift it. The system creates drag items with the selected text.
**Drop:** Text views accept dropped text, inserting at the position under the user's finger. The caret tracks the drag position.
**Move vs copy:**- Same text view → move (dragged text removed from original position)- Different view or app → copy
### iPhone vs iPad
```swift// iPad: drag enabled by default// iPhone: drag DISABLED by defaulttextView.textDragInteraction?.isEnabled = true // Enable on iPhone```
## UITextDragDelegate
All methods are optional. Set via `textView.textDragDelegate = self`.
### Providing Custom Drag Items
```swiftfunc textDraggableView(_ textDraggableView: UIView & UITextDraggable, itemsForDrag dragRequest: UITextDragRequest) -> [UIDragItem] { // Return custom items (e.g., add image alongside text) let text = dragRequest.suggestedItems.first?.localObject as? String ?? "" let textItem = UIDragItem(itemProvider: NSItemProvider(object: text as NSString))
// Add a custom representation let customData = encodeRichFormat(for: dragRequest.dragRange) let customItem = UIDragItem(itemProvider: NSItemProvider( item: customData as NSData, typeIdentifier: "com.myapp.richtext" ))
return [textItem, customItem]}```
### Disabling Drag
```swift// Option A: Return empty arrayfunc textDraggableView(_ textDraggableView: UIView & UITextDraggable, itemsForDrag dragRequest: UITextDragRequest) -> [UIDragItem] { return [] // Disables drag}
// Option B: Disable the interactiontextView.textDragInteraction?.isEnabled = false```
### Custom Drag Preview
```swiftfunc textDraggableView(_ textDraggableView: UIView & UITextDraggable, dragPreviewForLiftingItem item: UIDragItem, session: UIDragSession) -> UITargetedDragPreview? { // Return nil for default preview // Return custom UITargetedDragPreview for custom appearance return nil}```
### UITextDragPreviewRenderer
Text-aware preview rendering that understands multi-line text geometry:
```swift// Create a renderer from layout manager and rangelet renderer = UITextDragPreviewRenderer( layoutManager: textView.layoutManager, range: selectedRange)
// Adjust the preview rectanglesrenderer.adjust(firstLineRect: &firstRect, bodyRect: &bodyRect, lastLineRect: &lastRect, textOrigin: origin)
// The renderer provides proper multi-line drag previews// that follow the text's line geometry```
### Strip Color from Previews
```swifttextView.textDragOptions = .stripTextColorFromPreviews// Renders drag preview in uniform color instead of preserving text colors```
### Lifecycle Hooks
```swiftfunc textDraggableView(_ textDraggableView: UIView & UITextDraggable, dragSessionWillBegin session: UIDragSession) { // Drag is starting — pause syncing, show visual feedback}
func textDraggableView(_ textDraggableView: UIView & UITextDraggable, dragSessionDidEnd session: UIDragSession) { // Drag ended — resume normal operations}```
## UITextDropDelegate
All methods are optional. Set via `textView.textDropDelegate = self`.
### Controlling Drop Behavior
```swiftfunc textDroppableView(_ textDroppableView: UIView & UITextDroppable, proposalForDrop drop: UITextDropRequest) -> UITextDropProposal { // Check if this is a same-view drop (move vs copy) if drop.isSameView { return UITextDropProposal(dropAction: .insert) }
// Accept external drops as insert at drop position return UITextDropProposal(dropAction: .insert)}```
### UITextDropProposal Actions
| Action | Behavior ||--------|----------|| `.insert` | Insert at drop position (default) || `.replaceSelection` | Replace the current text selection || `.replaceAll` | Replace all text in the view |
```swift// Replace selection on droplet proposal = UITextDropProposal(dropAction: .replaceSelection)
// Optimize same-view operationsproposal.useFastSameViewOperations = true```
### Handling the Drop
```swiftfunc textDroppableView(_ textDroppableView: UIView & UITextDroppable, willPerformDrop drop: UITextDropRequest) { // Called just before the drop executes // Use for validation, logging, or pre-processing}```
### Non-Editable Text Views
By default, non-editable text views reject drops. Override with:
```swiftfunc textDroppableView(_ textDroppableView: UIView & UITextDroppable, willBecomeEditableForDrop drop: UITextDropRequest) -> UITextDropEditability { return .temporary // Become editable just for this drop, then revert // .no — reject the drop (default for non-editable) // .yes — become permanently editable}```
### Custom Drop Preview
```swiftfunc textDroppableView(_ textDroppableView: UIView & UITextDroppable, previewForDroppingAllItemsWithDefault defaultPreview: UITargetedDragPreview) -> UITargetedDragPreview? { // Return nil for default animation // Return custom preview for custom drop animation return nil}```
## macOS (AppKit)
macOS text drag/drop uses a completely different architecture — no UITextDragDelegate equivalent.
### NSTextView Default Behavior
NSTextView uses `NSDraggingSource` and `NSDraggingDestination` (which NSView conforms to). By default:- Text can be dragged from selections- Text drops are accepted if the view is editable- File drops are only accepted if `isRichText` and `importsGraphics` are both enabled
### Customizing on macOS
```swiftclass CustomTextView: NSTextView { // Accept additional pasteboard types override var acceptableDragTypes: [NSPasteboard.PasteboardType] { var types = super.acceptableDragTypes types.append(.init("com.myapp.richtext")) return types }
// Handle the drop override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool { let pasteboard = draggingInfo.draggingPasteboard if let customData = pasteboard.data(forType: .init("com.myapp.richtext")) { insertCustomContent(customData) return true } return super.performDragOperation(draggingInfo) }}```
### Field Editor Gotcha
During NSTextField editing, drops go to the **field editor** (shared NSTextView), not the NSTextField itself. To intercept:
```swiftfunc windowWillReturnFieldEditor(_ sender: NSWindow, to client: Any?) -> Any? { if client is MyTextField { return myCustomFieldEditor // Subclass NSTextView with custom drop handling } return nil}```
## Custom UITextInput Views
`UITextDraggable` and `UITextDroppable` are **NOT automatically adopted** by custom UITextInput views. Only UITextField and UITextView get them.
For custom views, use the general drag/drop APIs:
```swiftclass CustomEditor: UIView, UITextInput { override init(frame: CGRect) { super.init(frame: frame)
// Add general drag/drop interactions let dragInteraction = UIDragInteraction(delegate: self) addInteraction(dragInteraction)
let dropInteraction = UIDropInteraction(delegate: self) addInteraction(dropInteraction) }}
extension CustomEditor: UIDragInteractionDelegate { func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: any UIDragSession) -> [UIDragItem] { guard let selectedText = textInSelectedRange() else { return [] } let provider = NSItemProvider(object: selectedText as NSString) return [UIDragItem(itemProvider: provider)] }}
extension CustomEditor: UIDropInteractionDelegate { func dropInteraction(_ interaction: UIDropInteraction, performDrop session: any UIDropSession) { // Handle the drop using UITextInput methods // Convert drop point to UITextPosition let point = session.location(in: self) guard let position = closestPosition(to: point) else { return } // Insert text at position }}```
## Platform Comparison
| Feature | iOS (UITextView) | macOS (NSTextView) ||---------|------------------|-------------------|| Text drag/drop protocols | UITextDraggable / UITextDroppable | NSDraggingSource / NSDraggingDestination || Specialized delegates | UITextDragDelegate / UITextDropDelegate | None (general dragging APIs) || Drop proposal system | UITextDropProposal with actions | performDragOperation override || Multi-line preview | UITextDragPreviewRenderer | System-provided || iPhone drag | Disabled by default | N/A || File drops on text | Supported | Only if isRichText + importsGraphics || Move vs copy | Automatic (same view = move) | Manual via operation mask |
## Common Pitfalls
1. **Drag not working on iPhone** — `textDragInteraction?.isEnabled` defaults to `false` on iPhone. Must enable explicitly.2. **Non-editable views rejecting drops** — Implement `willBecomeEditableForDrop` returning `.temporary` to accept drops on read-only views.3. **Custom UITextInput views have no text drag/drop** — Must add UIDragInteraction/UIDropInteraction manually. The text-specific protocols only apply to UITextField and UITextView.4. **Attributed text ignored in drag previews** — Known issue (rdar://34098227). Provide custom drag items to ensure attributed text is included.5. **macOS field editor intercepts drops** — Drops during NSTextField editing go to the field editor, not the text field. Provide a custom field editor to intercept.6. **Move vs copy confusion** — Same-view drops default to move (source text deleted). Cross-view defaults to copy. Check `drop.isSameView` in your proposal.7. **NSTextView not accepting file drops** — Both `isRichText` and `importsGraphics` must be enabled for file drop acceptance on macOS.
## Related Skills
- Use `/skill apple-text-pasteboard` for copy/paste (synchronous clipboard operations).- Use `/skill apple-text-interaction` for other gesture and interaction customization.- Use `/skill apple-text-input-ref` for the UITextInput protocol that custom drag/drop builds on.- Use `/skill apple-text-attachments-ref` when dropped content becomes inline attachments.