3.7 KiB
Itinerary Editor
What This Is
An interactive drag-and-drop itinerary editor for the SportsTime iOS app. Users can rearrange travel segments and custom items within their trip itinerary while respecting game schedules and city-based travel constraints. Built as a UITableView bridged into SwiftUI to enable precise drag-and-drop with insertion line feedback.
Core Value
Drag-and-drop that operates on semantic positions (day + sortOrder), not row indices — so user intent is preserved across data reloads.
Requirements
Validated
- ✓ Trip itineraries display with day headers and games — existing (
TripDetailView) - ✓ Conflict detection for same-day games in different cities — existing
- ✓ SwiftUI + SwiftData architecture — existing
Active
- Semantic position model using
(day: Int, sortOrder: Double)for all movable items - Custom items can be placed anywhere within any day (including between games)
- Travel segments respect city constraints (after from-city games, before to-city games)
- Travel segments respect day range constraints (within valid travel window)
- Invalid drops are rejected with snap-back behavior
- Insertion lines show precise drop targets during drag
- External drops (from outside the table) work with same semantic rules
- Position persists correctly across data reloads
- No visual jumpiness or dead zones during drag operations
Out of Scope
- Reordering games — games are fixed by schedule
- Reordering day headers — structural, one per day
- Zone-based drop highlighting — using insertion lines instead
- Multi-day travel segments — travel belongs to exactly one day
Context
Existing codebase: SportsTime iOS app with Clean MVVM architecture. TripDetailView already displays itineraries with conflict detection. The new editor replaces the display-only view with an interactive one.
Technical environment:
- iOS 26+, Swift 6 concurrency
- SwiftUI drives data, UITableView handles drag-and-drop
- SwiftData for persistence
- Frequent reloads from data changes; visual-only state is not acceptable
What went wrong in previous attempts:
- Row-based snapping instead of semantic (day, sortOrder)
- Treating travel as structural ("travelBefore") instead of positional
- Losing sortOrder for travel during flattening
- Hard-coded flatten order (header → games → customs) that ignored sortOrder
- Drag logic and reload logic fighting each other
Constraints
- Architecture: SwiftUI wrapper must drive data; UIKit table handles drag/drop mechanics
- Persistence: All positions must be semantic (day, sortOrder) — no ephemeral visual state
- Reload tolerance: Reloads must not undo valid user actions; position must survive reload
- iOS version: iOS 26+ (per existing app target)
Key Decisions
| Decision | Rationale | Outcome |
|---|---|---|
| UITableView over SwiftUI List | SwiftUI drag-and-drop lacks insertion line precision and external drop support | — Pending |
| (day, sortOrder) position model | Row indices break on reload; semantic position is reload-stable | — Pending |
| Insertion lines (not zones) | User wants precise feedback on exact drop location | — Pending |
| Custom items interleave with games | Maximum flexibility for user — can add notes between games | — Pending |
| Travel position-constrained within day | After from-city games, before to-city games on same day | — Pending |
| Invalid drops rejected (snap back) | Cleaner than auto-clamping; user knows exactly what happened | — Pending |
| Items always belong to a day | No liminal "between days" state; visual gap is end of previous day | — Pending |
Last updated: 2026-01-18 after initialization