Skip to content

Anchor

Accessible indoor navigation for iPhone: step-free routing, spoken landmark guidance, and LiDAR proximity alerts.

Anchor helps wheelchair users and blind and low-vision students get through campus buildings. It plans step-free routes, guides people landmark by landmark with speech, and has an experimental LiDAR mode that warns about nearby surfaces. Two rules shaped the whole design: never suggest a route the data can't support, and never invent directions.

Context
Team project, UB Forge
Status
In development, Oct 2026 – Present
My role
Designing step-free routing and spoken landmark guidance in SwiftUI; prototyping the LiDAR proximity alerts.
Stack
  • Swift
  • SwiftUI
  • ARKit
  • RealityKit
  • AVFoundation
  • XCTest
  • JavaScript
  • Playwright
  • GitHub Actions
Anchor step-free route diagramTwo floor plans. The highlighted route leaves North Plaza by the west ramp, crosses Floor 1 to Elevator A, rides to Floor 2, and ends at Room 201. Both staircases are drawn dashed because step-free mode excludes them.Floor 1Floor 2North PlazaRoom 201Elevator AStairwell A
Step-free routeStairs, excludedAnchor routing North Plaza to Room 201 on its demo map: 7 connections, 87 m recorded, zero stairs.

Architecture

The app is split so the parts that have to be correct can be tested without a phone. Core contains the map model, validation, routing, step-by-step guidance, and depth math, using only Foundation: no UI, no ARKit. A small Swift package wraps Core so its tests run with swift test on macOS or Linux, no simulator required.

Services wrap platform APIs (settings in UserDefaults, speech, haptics), and Features holds the SwiftUI screens for Routes, Nearby, and Settings. Shared state lives in one observable app model.

How a route request flows through the app
  1. demo_map.json

    Loaded by CampusMap and checked by MapValidator before anything is routed.

  2. RoutePlanner

    Dijkstra on the directed graph, with the step-free preference from Settings.

  3. Then one of:

    • Route

      Summary, then RouteGuidance walks the user through each landmark, spoken by SpeechService or VoiceOver.

    • NoRouteDiagnosis

      “No suitable route found,” plus the specific connections that block the trip.

Routing rules

Each bidirectional connection becomes two directed edges and a one-way connection becomes one, so an exit-only door is used leaving the building but never entering it.

  • Step-free mode excludes anything not marked “confirmed” step-free. Stairs are excluded even if the data wrongly marks them confirmed, and the validator rejects that data anyway.
  • Unavailable connections are always excluded. Unknown availability is allowed but shown as a “Before you go” warning.
  • No silent fallback: if no route meets the preferences, the planner re-runs with blocked connections allowed but penalized, purely to explain which ones caused the failure. That relaxed route is never offered.
  • Instructions are the authored text from the map. Lengths appear only when recorded, and no compass turns are generated.
No suitable route found. Blocking connections: Elevator A (unavailable). Step-free routes are on, so the app will not substitute a route with stairs.
Elevator outage with step-free on: the app names the blocker instead of offering stairs.
Route summary from North Plaza to Room 201: step-free only, 8 steps to confirm, 87 meters recorded plus one connection without a recorded length.
Step-free route summary. The elevator has no recorded length, so none is shown.

Map data model

Every connection records physical suitability (step-free: confirmed, unsuitable, or unknown) separately from right-now availability (available, unavailable, unknown), plus a verification status, source, and who checked it and when. Display length and routing cost are separate fields, so elevator wait time can affect routing without ever being shown as a distance.

  • A fictional demo map may only contain simulated connections.
  • A verified connection must say when and by whom it was checked.
  • Stairs can never be marked step-free.

LiDAR proximity alerts

Nearby reads ARKit's scene depth for the center of the camera view. Depth processing is pure math kept apart from ARKit, so it is unit-tested with hand-built arrays.

Depth pipeline, camera to alert
  1. ARSession delegate

    Background queue, up to 10 Hz. Copies the center 30% of each depth and confidence map.

  2. DepthAnalyzer

    Drops low-confidence and out-of-range pixels, then takes a 5th-percentile nearest distance.

  3. NearbyViewModel

    Main actor, ticks every 0.2 s and hides readings older than 0.6 s.

  4. ProximityAlertPolicy

    Decides when to fire haptics or speech, with hysteresis and rate limiting.

  • Pixels below medium confidence or outside 0.1–5 m are dropped; if fewer than 25% remain, there is no reading rather than a guess.
  • The nearest distance is the 5th percentile of usable depths, which ignores a few noisy pixels without missing a real surface.
  • Alerts fire once on entering the threshold, repeat at most every 3 s, and re-arm only after the distance clears the threshold by 0.25 m, so a reading hovering at the edge can't flood the user.
  • If reliable readings stop for 1 s, the user hears that depth readings are unavailable, so silence never means “all clear” by accident.

Accessibility

  • All text uses system text styles, so Dynamic Type works; guidance buttons stack vertically at large sizes. Primary buttons are 44–56 pt tall.
  • With VoiceOver on, each new step moves focus to the instruction so VoiceOver reads it, and the app doesn't also speak it, avoiding two voices at once.
  • Without VoiceOver, AVSpeechSynthesizer speaks in navigation-prompt mode and ducks other audio.
  • System colors only, so contrast follows iOS in light and dark mode.
Guidance screen: start at North Plaza, with an arrival cue describing a paved area with a bike rack and benches, and a button to confirm arrival.
Landmark guidance: the user confirms each arrival cue before moving on.

Testing

  • 40 XCTest unit tests cover the map model, validation, Dijkstra routing, guidance, depth math, and alert logic, including every demo scenario.
  • A zero-dependency browser companion lets teammates on Windows and Linux review flows. It has 21 Node tests for routing and the local server, and 11 Playwright tests that include automated WCAG A/AA checks with axe in light and dark mode.
  • GitHub Actions runs the web checks on every relevant push and pull request.

Status and limits

Anchor is at its first milestone, building toward a December 2026 demo at 43North. The map is fictional demo data, and every route screen says so. There is no indoor positioning yet (people confirm each landmark themselves) and no object recognition; Nearby measures distance to a surface, not what it is.