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>
10 KiB
Project Research Summary
Project: SportsTime Itinerary Editor Domain: iOS drag-drop reordering with semantic positioning Researched: 2026-01-18 Confidence: HIGH
Executive Summary
Building a drag-drop itinerary editor for iOS requires bridging two coordinate systems: UITableView's row indices (visual) and the semantic model of (day, sortOrder) (business logic). The SportsTime codebase already contains a working UITableView-based implementation with UIHostingConfiguration for SwiftUI cells. This research validates that approach and identifies the key architectural decision that makes or breaks the feature: row indices are ephemeral display concerns; semantic positions (day, sortOrder) are the source of truth.
The recommended approach extends the existing implementation rather than replacing it. UITableView's native drag-drop APIs (iOS 11+) provide superior UX compared to SwiftUI-only solutions: real-time insertion line feedback, proper scroll-while-dragging, and constraint validation during drag. The existing canMoveRowAt/moveRowAt pattern handles internal reordering well. For external drops (e.g., from a suggestions panel), add UITableViewDropDelegate conformance.
The critical risks are all related to confusing row indices with semantic positions. Previous attempts failed because travel was treated as a structural day property rather than a positioned item, flattening ignored sortOrder values, and drag logic fought reload logic. The architecture must enforce strict separation: row indices exist only during display, semantic positions exist in the data model, and the bridge between them is recalculated on every flatten operation.
Key Findings
Recommended Stack
The existing UIKit + SwiftUI hybrid pattern is correct. UITableView provides the drag-drop infrastructure; SwiftUI provides the cell content through UIHostingConfiguration.
Core technologies:
- UITableViewController: Native drag handles, real-time insertion feedback, proven since iOS 11
- UIHostingConfiguration: Embeds SwiftUI views in UIKit cells without wrapper hacks
- UITableViewDropDelegate: Required for accepting external drops (not internal reorders)
- UIViewControllerRepresentable + Coordinator: Bridge pattern already working in codebase
What to avoid:
- SwiftUI-only drag-drop (
.draggable(),.dropDestination()) - lacks insertion line feedback - Third-party reordering libraries - compatibility issues, unnecessary dependency
- iOS 26 SwiftUI drag modifiers - promising but not mature enough for complex constraints
Expected Features
Must have (table stakes):
- Lift animation on grab (shadow + scale)
- Ghost/placeholder at original position
- Insertion indicator line between items
- Items shuffle out of the way (100ms animation)
- Magnetic snap on drop
- Invalid drop feedback (animate back to origin)
- Haptic feedback on grab and drop
- Auto-scroll when dragging to viewport edge
Should have (polish):
- Slight tilt on drag (Trello-style, 2-3 degrees)
- Keyboard reordering for accessibility (VoiceOver actions)
- Undo after drop (toast with 5-second timeout)
- Drag handle icon (visual affordance)
Defer (overkill for itinerary):
- Drag between screens
- Multi-item drag with count badge
- Physics-based spring animations
- Custom drag preview images
Architecture Approach
The architecture uses five layers that cleanly separate concerns. Each layer has a single responsibility, making the system resilient to the frequent reloads from SwiftUI state changes.
Major components:
- Semantic Position Model (
ItineraryItem) - Source of truth with day and sortOrder - Constraint Validation (
ItineraryConstraints) - Determines valid positions per item type - Visual Flattening - Transforms semantic items into flat row array
- Drop Slot Calculation - Translates row indices back to semantic positions
- Drag Interaction - UITableView delegate methods with constraint snapping
Key pattern: Midpoint insertion for sortOrder (1.0, 2.0 -> 1.5 -> 1.25 etc.) enables unlimited insertions without renumbering existing items.
Critical Pitfalls
-
Row Index vs Semantic Position Confusion - Never store row indices as positions. Row indices are ephemeral; semantic (day, sortOrder) is persistent. Address in Phase 1 data model.
-
Travel as Structural Instead of Positional - Travel must be an item with its own (day, sortOrder), not a day property like
travelBefore. Use sortOrder < 0 for "before games" convention. -
Hard-Coded Flatten Order - Flattening MUST sort by sortOrder within each day. Hard-coding "header, travel, games, custom" ignores sortOrder and breaks reload.
-
Data Out of Sync During Drag - Never call
reloadData()while drag is active. Guard SwiftUI updates withdraggingItem != nilflag. -
Coordinate Space Confusion - UITableView's
targetIndexPathuses "proposed" coordinates (source row removed). Pre-compute valid destinations in proposed space at drag start.
Implications for Roadmap
Based on research, suggested phase structure:
Phase 1: Semantic Position Model
Rationale: Everything depends on getting the data model right. Previous failures stemmed from row-based thinking.
Delivers: ItineraryItem with day: Int and sortOrder: Double, travel as positioned item
Addresses: Table stakes data representation
Avoids: Row Index vs Semantic Position Confusion, Travel as Structural pitfalls
Phase 2: Constraint Validation Engine
Rationale: Constraints must be validated semantically, not by row index. Build this before drag interaction.
Delivers: ItineraryConstraints that determines valid positions for games (fixed), travel (bounded), custom (any)
Uses: Semantic position model from Phase 1
Implements: Constraint validation layer
Phase 3: Visual Flattening
Rationale: Needs semantic model and constraint awareness. Bridge between model and display. Delivers: Deterministic flatten algorithm that sorts by sortOrder, produces flat row array Addresses: Hard-coded flatten order pitfall Implements: Flattening layer with sortOrder < 0 / >= 0 split
Phase 4: Drag Interaction
Rationale: Depends on all previous layers. This is where UIKit integration happens. Delivers: Working drag-drop with constraint snapping, haptics, insertion line Uses: UITableViewDragDelegate/DropDelegate, flattening, constraints Avoids: Data sync during drag, coordinate space confusion pitfalls
Phase 5: Polish and Edge Cases
Rationale: Core functionality first, polish second. Delivers: Lift animation, ghost placeholder, auto-scroll, accessibility actions Addresses: All remaining table stakes features
Phase 6: External Drops (Optional)
Rationale: Only if accepting drops from outside the table (e.g., suggestions panel)
Delivers: UITableViewDropDelegate conformance for external items
Uses: Same constraint validation and drop slot calculation
Phase Ordering Rationale
- Data model first (Phase 1-2): The architecture analysis identified semantic positioning as the foundation. Constraints depend on semantics, not rows.
- Flatten before drag (Phase 3): Drag operations call flatten after every move. Getting flatten right prevents the "drag logic vs reload logic" battle.
- Interaction last (Phase 4-6): UITableView delegate methods are the integration point. They consume all other layers.
Research Flags
Phases likely needing deeper research during planning:
- Phase 4: Coordinate space translation is subtle. May need prototype to validate proposed vs current index handling.
- Phase 6: External drops require NSItemProvider/Transferable patterns. Research if implementing.
Phases with standard patterns (skip research-phase):
- Phase 1-2: Data modeling is straightforward once semantics are understood.
- Phase 3: Flattening is deterministic algorithm, well-documented in existing code.
- Phase 5: Polish features are standard iOS patterns.
Confidence Assessment
| Area | Confidence | Notes |
|---|---|---|
| Stack | HIGH | Existing implementation validates approach, APIs stable since iOS 11 |
| Features | HIGH | Multiple authoritative UX sources (NN Group, Atlassian, Apple HIG) agree |
| Architecture | HIGH | Based on existing working codebase analysis |
| Pitfalls | HIGH | Documented previous failures + Apple documentation |
Overall confidence: HIGH
Gaps to Address
- iOS 26 SwiftUI drag modifiers: New in WWDC 2025, limited production experience. Assess if they mature enough to replace UIKit approach in future versions.
- Mac Catalyst support: NSItemProvider quirks noted. Validate if targeting Catalyst.
- sortOrder precision exhaustion: Theoretical concern after thousands of insertions. Implement normalize function if needed (unlikely in practice).
Critical Insight
The ONE most important thing: Row indices are lies. They change constantly as items are added, removed, reordered, and the table flattens. The semantic model (day, sortOrder) is truth. Every previous failure traced back to treating row indices as positions. Every function that touches positions must speak semantic coordinates, converting to/from row indices only at the UITableView boundary.
Sources
Primary (HIGH confidence)
- Apple: Supporting drag and drop in table views
- Apple: UITableViewDragDelegate
- Apple: UITableViewDropDelegate
- Existing codebase:
ItineraryTableViewController.swift,ItineraryTableViewWrapper.swift,ItineraryConstraints.swift
Secondary (MEDIUM confidence)
- Smart Interface Design Patterns - Drag and Drop UX
- Atlassian Pragmatic Drag and Drop Design Guidelines
- Nielsen Norman Group - Drag and Drop
- Apple Human Interface Guidelines - Drag and Drop
Tertiary (LOW confidence)
- iOS 26 SwiftUI drag modifiers documentation (new APIs, limited production validation)
- Third-party library compatibility reports (community anecdotes)
Research completed: 2026-01-18 Ready for roadmap: yes