Platforms
SwiftUI mapping
How each token and component maps to SwiftUI.
| Piece | Implementation |
|---|---|
| Colours | Asset catalog colour sets with Any / Dark appearances, named after the tokens |
| Line colours | CTA hue tokens with contrast variants: Yellow darkens in light, Brown/Purple lighten in dark |
| Type | Font.custom("Figtree", size:, relativeTo:) wrapped in Font.ct(.display | .title | .body | .sub | .cap) |
| RouteIconView | Circle or capsule at .mini / .standard / .large |
| StationIconView | HStack(spacing: 0) in a Capsule filled card2 for every station: CTIconView(icon: .train) at 75% in a square frame of the height, then HStack(spacing: 1.5) of RoundedRectangle(cornerRadius: 0.09h) per line at 0.225h × 0.6h with a −0.08h leading offset; trailing padding max(4, 0.22h) |
| VehicleMarkerView | ZStack in a 44 frame, hit testing off: a separate VehicleArrowShape offset 20.5 and rotated to the bearing, then the body (train: route-colour Circle; bus: RoundedRectangle(cornerRadius: 7, style: .continuous) in Brand) with a 2pt Paper keyline (Ink when selected; a bus adds a 1 Paper hairline inside) and an upright glyph. Stale fills the body Muted |
| LineBarView | HStack(spacing: 3) of fixed 10 × 24 active line-colour RoundedRectangles(cornerRadius: 3); no border, no count-based resizing |
| Line order | Pure function of the set of line IDs; compute once and cache |
| Motion | One sheet spring (DesignTokens): UISpringTimingParameters(duration: 0.42, bounce: 0.15), damping 0.85 for FloatingPanel. Reduce Motion: 0.17 s critically damped, or a 0.17 s fade for the station slide |
| Hit areas | .frame(minWidth: 44, minHeight: 44).contentShape(Rectangle()) on every Button and map Annotation |
| RouteFilter | Single-select binding holding All or one route ID; selected state uses the shared selection halo |
| Detail overlays | The stop detail is its own FloatingPanel added above the page sheet. It slides up at .half while the page sheet moves to .hidden on the same spring; dismissing reverses both and restores the stored page and MapSheetDetent. Search is a page of the same sheet at .full |
| Core sheet | One FloatingPanel over the persistent Map: .tip is 180pt from the safe-area bottom, .half is fractional 0.5, and .full is 4pt from the safe-area top; initial .half; 6pt leading/trailing/bottom container margins; 28pt continuous corner radius; backdrop alpha 0; removal disabled. MapFloatingPanelBehavior rejects momentum projection across anchors; redirectional progress allows only a drag that physically crosses half to continue. Rubber-banding and scroll tracking remain on. CoreSheetView constrains content to the visible detent and the system tab bar floats over it |
| Camera | MapView owns camera moves: rider gestures, MapUserLocationButton, the Nearby low-to-up transition, opening a stop (centre above the half sheet, 1,400 m minimum zoom, .smooth 0.45 s), the title-tap re-pan (CenterStopOnMapAction environment value) and the arrival-tap pan to a vehicle. Sheet detent changes never assign camera position |
| Navigation and controls | MainView owns one persistent MapView. MapPanelModel holds the Home or Nearby base page plus Search and stop overlay state. CoreTabBar is a system TabView (Home, Nearby, and Search in the search role) whose empty tabs only choose the page. CoreSheetView renders a person.crop.circle profile button at the top trailing corner. Search route roots use NavigationStack inside the overlay. The Map keeps system .mapControls; searchable, Form, Toggle, Picker, toolbar buttons, alerts and confirmation dialogs remain native. |
| Icons | System controls use SF Symbols; transit presentation uses CTIconView, with RoundedStarShape for Favorite. Train, Bus and Train + bus are template-rendered asset catalog images synced from icons/ with the same names |