Files: - STACK.md - UITableView drag-drop APIs, SwiftUI bridging patterns - FEATURES.md - Table stakes UX (lift, insertion line, haptics), polish features - ARCHITECTURE.md - 5-layer semantic positioning architecture - PITFALLS.md - Critical pitfalls (row vs semantic, travel as structural) - SUMMARY.md - Executive synthesis with roadmap implications Key findings: - Stack: UITableView + UIHostingConfiguration (existing pattern validated) - Architecture: Semantic (day, sortOrder) model, not row indices - Critical pitfall: Row indices are ephemeral; semantic positions are truth Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
Features Research: Drag-Drop Editor UX
Domain: Drag-and-drop itinerary editor for iOS sports travel app Researched: 2026-01-18 Confidence: HIGH (multiple authoritative sources cross-verified)
Executive Summary
Polished drag-drop requires deliberate visual feedback at every state transition. The difference between "feels broken" and "feels delightful" comes down to: lift animation, predictable insertion indicators, smooth reshuffling, and magnetic snap-to-place. Your constraints (fixed day headers, fixed games, movable travel/custom items) add complexity but are achievable with proper drop zone logic.
Table Stakes
Features users expect. Missing any of these makes the editor feel broken.
| Feature | Why Expected | Complexity | Implementation Notes |
|---|---|---|---|
| Lift animation on grab | Users expect physical metaphor - picking up an object | Low | Elevation (shadow), scale 1.02-1.05x, slight z-offset |
| Ghost/placeholder at origin | Shows where item came from, reduces anxiety | Low | Semi-transparent copy or outlined placeholder in original position |
| Insertion indicator line | Must show exactly where item will drop | Medium | Horizontal line with small terminal bleeds, appears between items |
| Items move out of the way | Preview of final state while dragging | Medium | ~100ms animation, triggered when dragged item center overlaps edge |
| Magnetic snap on drop | Satisfying completion, confirms action worked | Low | 100ms ease-out animation to final position |
| Clear invalid drop feedback | Don't leave user guessing why drop failed | Low | Item animates back to origin if dropped in invalid zone |
| Touch hold delay (300-500ms) | Distinguish tap from drag intent | Low | iOS standard; prevents accidental drags |
| Haptic on grab | Tactile confirmation drag started | Low | UIImpactFeedbackGenerator.light on pickup |
| Haptic on drop | Tactile confirmation action completed | Low | UIImpactFeedbackGenerator.medium on successful drop |
| Scroll when dragging to edge | Lists longer than viewport need auto-scroll | Medium | Scroll speed increases closer to edge, ~40px threshold |
Insertion Indicator Details
The insertion line is critical. Best practices:
- Appears between items (in the gap), not on top
- Has small terminal bleeds (~4px) extending past item edges
- Triggered when center of dragged item crosses edge of potential neighbor
- Color should contrast clearly (system accent or distinct color)
Animation Timing
| Event | Duration | Easing |
|---|---|---|
| Lift (pickup) | 150ms | ease-out |
| Items shuffling | 100ms | ease-out |
| Snap to place (drop) | 100ms | ease-out |
| Return to origin (cancel) | 200ms | ease-in-out |
Nice-to-Have
Polish features that delight but aren't expected.
| Feature | Value | Complexity | Notes |
|---|---|---|---|
| Slight tilt on drag (2-3 degrees) | Trello's signature polish; makes interaction feel playful | Low | Rotate3D effect, matches brand personality |
| Progressive drop zone highlighting | Visual intensifies as item approaches valid zone | Medium | Background color change, border enhancement |
| Multi-item drag with count badge | Power users moving multiple items at once | High | Not needed for v1; itinerary items are usually moved one at a time |
| Keyboard reordering (a11y) | Up/Down arrows via rotor actions | Medium | Important for accessibility; add accessibilityActions |
| Undo after drop | Recover from mistakes | Medium | Toast with "Undo" button, ~5 second timeout |
| Drag handle icon | Visual affordance for draggability | Low | 6-dot grip icon (Notion-style) or horizontal lines |
| Cancel drag with escape/shake | Quick abort | Low | Shake-to-cancel on iOS; return to origin |
| Drop zone "ready" state | Zone visually activates before item enters | Low | Subtle background shift when drag starts |
Tilt Animation (Trello-style)
The 2-3 degree tilt on dragged items is considered "gold standard" polish:
- Adds personality without being distracting
- Reinforces physical metaphor (picking up a card)
- Should match your app's design language (may be too playful for some apps)
Overkill
Skip these - high complexity, low value for an itinerary editor.
| Feature | Why Skip | What to Do Instead |
|---|---|---|
| Drag between sections/screens | Your items live within days; cross-day moves are rare | Allow within same list only, or use "Move to..." action menu |
| Nested drag-drop | Games within days is hierarchy enough | Keep flat list per day section |
| Free-form canvas positioning | Not applicable to linear itinerary | Stick to list reordering |
| Real-time collaborative drag | Massive sync complexity | Single-user editing |
| Drag-to-resize | Items don't have variable size | Fixed item heights |
| Custom drag preview images | Native preview is sufficient | Use default lifted appearance |
| Physics-based spring animations | Overkill for list reordering | Simple ease-out is fine |
Interactions to Support
Specific drag scenarios for your itinerary context.
Scenario 1: Move Custom Item Within Same Day
User intent: Reorder "Dinner at Lou Malnati's" from after to before the Cubs game
Expected behavior:
- Long-press on custom item (300ms) - haptic feedback
- Item lifts (shadow + scale), ghost remains at origin
- Drag within day section - insertion line appears between valid positions
- Games and travel segments shuffle with 100ms animation
- Drop - item snaps into place, haptic confirms
Constraints:
- Custom item can move anywhere within the day
- Cannot move before/after day header
- Cannot replace or overlay a game (games are fixed)
Scenario 2: Move Custom Item to Different Day
User intent: Move hotel check-in from Day 2 to Day 1
Expected behavior:
- Long-press and lift
- Drag toward Day 1 section
- Auto-scroll if Day 1 is off-screen
- Insertion line appears at valid positions in Day 1
- Day 2 collapses to show item removed; Day 1 expands
- Drop - item now in Day 1
Constraints:
- Can cross day boundaries
- Still cannot land on games
Scenario 3: Move Travel Segment (Constrained)
User intent: Move "Drive: Chicago to Milwaukee" earlier in the day
Expected behavior:
- Long-press on travel segment
- Item lifts (possibly with different visual treatment since it's constrained)
- Insertion line only appears at valid positions (before/after games it connects)
- Invalid positions show no insertion line (or dimmed indicator)
- If dropped at invalid position, item animates back to origin
Constraints:
- Travel segments connect stadiums/locations
- Can only move within logical route order
- Must validate position before showing insertion indicator
Scenario 4: Attempt to Move Fixed Item (Game)
User intent: User tries to drag a game (not allowed)
Expected behavior:
- Long-press on game item
- No lift animation - item doesn't respond as draggable
- Optionally: subtle shake or tooltip "Games cannot be reordered"
- User understands this item is fixed
Visual differentiation:
- Fixed items should NOT have drag handles
- Could have different visual treatment (no grip icon, different background)
Scenario 5: Drag to Invalid Zone
User intent: User drags custom item but releases over a game
Expected behavior:
- Item is being dragged
- Hovers over game - no insertion line appears (invalid)
- User releases
- Item animates back to origin (~200ms)
- Optional: brief error state or haptic warning
Visual States Summary
| Element State | Visual Treatment |
|---|---|
| Resting (draggable) | Normal appearance, optional drag handle icon on hover/focus |
| Resting (fixed) | Normal, but NO drag handle; visually distinct |
| Lifted/grabbed | Elevated (shadow), slight scale up (1.02-1.05), optional tilt |
| Ghost at origin | Semi-transparent (30-50% opacity) or outlined placeholder |
| Insertion line | Accent-colored horizontal line, ~2px height, bleeds past edges |
| Invalid drop zone | No insertion line; item over zone dims or shows warning |
| Drop zone ready | Subtle background color shift when any drag starts |
| Dropped/success | Snaps to place, haptic feedback, ghost disappears |
| Cancelled/error | Returns to origin with animation, optional warning haptic |
Accessibility Requirements
| Requirement | Implementation | Priority |
|---|---|---|
| VoiceOver reordering | accessibilityActions with "Move Up" / "Move Down" | High |
| Rotor integration | Actions appear in VoiceOver rotor | High |
| Focus management | Focus follows moved item after reorder | Medium |
| Live region announcements | Announce position change ("Item moved to position 3") | Medium |
| Fallback buttons | Optional up/down arrows as visual alternative | Low (nice to have) |
SwiftUI example for accessibility:
.accessibilityAction(named: "Move Up") { moveItemUp(item) }
.accessibilityAction(named: "Move Down") { moveItemDown(item) }
Mobile-Specific Considerations
| Concern | Solution |
|---|---|
| Fat finger problem | Minimum 44x44pt touch targets; drag handles at least 44pt wide |
| Scroll vs. drag conflict | Long-press delay (300-500ms) distinguishes intent |
| Viewport limitations | Auto-scroll at edges (40px threshold), speed increases near edge |
| One-handed use | Consider "Move to..." button as alternative to long-distance drags |
| Accidental drops | Generous drop zones; magnetic snap; undo option |
Anti-Patterns to Avoid
| Anti-Pattern | Why Bad | Do Instead |
|---|---|---|
| Edge-to-edge shuffle trigger | Feels "twitchy", items move unexpectedly | Use center-overlap-edge trigger |
| Instant reshuffle (no animation) | Disorienting, hard to track what moved | 100ms animated transitions |
| No ghost/placeholder | User loses context of original position | Always show origin indicator |
| Drag handle too small | Frustrating on touch | Minimum 44pt, ideally larger |
| Remove item during drag | Anxiety - "where did it go?" | Keep ghost visible at origin |
| Scroll too fast at edges | Overshoots, loses control | Gradual speed increase |
| No invalid feedback | User thinks interaction is broken | Clear visual/haptic for invalid drops |
Sources
High Confidence (verified with multiple authoritative sources):
- Smart Interface Design Patterns - Drag and Drop UX
- Atlassian Pragmatic Drag and Drop Design Guidelines
- Pencil & Paper - Drag & Drop UX Design Best Practices
- Nielsen Norman Group - Drag and Drop: How to Design for Ease of Use
Medium Confidence (single authoritative source):
- LogRocket - Designing Drag and Drop UIs
- Darin Senneff - Designing a Reorderable List Component
- Apple Human Interface Guidelines - Drag and Drop
Low Confidence (community patterns):
- Various SwiftUI implementation guides (verify APIs against current documentation)
- Trello UX patterns referenced in multiple articles (de facto standard)