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>
244 lines
12 KiB
Markdown
244 lines
12 KiB
Markdown
# 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:**
|
|
1. Long-press on custom item (300ms) - haptic feedback
|
|
2. Item lifts (shadow + scale), ghost remains at origin
|
|
3. Drag within day section - insertion line appears between valid positions
|
|
4. Games and travel segments shuffle with 100ms animation
|
|
5. 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:**
|
|
1. Long-press and lift
|
|
2. Drag toward Day 1 section
|
|
3. Auto-scroll if Day 1 is off-screen
|
|
4. Insertion line appears at valid positions in Day 1
|
|
5. Day 2 collapses to show item removed; Day 1 expands
|
|
6. 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:**
|
|
1. Long-press on travel segment
|
|
2. Item lifts (possibly with different visual treatment since it's constrained)
|
|
3. Insertion line only appears at **valid** positions (before/after games it connects)
|
|
4. Invalid positions show no insertion line (or dimmed indicator)
|
|
5. 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:**
|
|
1. Long-press on game item
|
|
2. **No lift animation** - item doesn't respond as draggable
|
|
3. Optionally: subtle shake or tooltip "Games cannot be reordered"
|
|
4. 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:**
|
|
1. Item is being dragged
|
|
2. Hovers over game - no insertion line appears (invalid)
|
|
3. User releases
|
|
4. Item animates back to origin (~200ms)
|
|
5. 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:
|
|
```swift
|
|
.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](https://smart-interface-design-patterns.com/articles/drag-and-drop-ux/)
|
|
- [Atlassian Pragmatic Drag and Drop Design Guidelines](https://atlassian.design/components/pragmatic-drag-and-drop/design-guidelines/)
|
|
- [Pencil & Paper - Drag & Drop UX Design Best Practices](https://www.pencilandpaper.io/articles/ux-pattern-drag-and-drop)
|
|
- [Nielsen Norman Group - Drag and Drop: How to Design for Ease of Use](https://www.nngroup.com/articles/drag-drop/)
|
|
|
|
**Medium Confidence (single authoritative source):**
|
|
- [LogRocket - Designing Drag and Drop UIs](https://blog.logrocket.com/ux-design/drag-and-drop-ui-examples/)
|
|
- [Darin Senneff - Designing a Reorderable List Component](https://www.darins.page/articles/designing-a-reorderable-list-component)
|
|
- [Apple Human Interface Guidelines - Drag and Drop](https://developer.apple.com/design/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)
|