Files
SportstimeAPI/.planning/research/SUMMARY.md
Trey t 6f09c0abcb docs: complete project research for itinerary editor
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>
2026-01-18 13:16:07 -06:00

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

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:

  1. Semantic Position Model (ItineraryItem) - Source of truth with day and sortOrder
  2. Constraint Validation (ItineraryConstraints) - Determines valid positions per item type
  3. Visual Flattening - Transforms semantic items into flat row array
  4. Drop Slot Calculation - Translates row indices back to semantic positions
  5. 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

  1. 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.

  2. 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.

  3. Hard-Coded Flatten Order - Flattening MUST sort by sortOrder within each day. Hard-coding "header, travel, games, custom" ignores sortOrder and breaks reload.

  4. Data Out of Sync During Drag - Never call reloadData() while drag is active. Guard SwiftUI updates with draggingItem != nil flag.

  5. Coordinate Space Confusion - UITableView's targetIndexPath uses "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)

Secondary (MEDIUM confidence)

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