Skip to content
CosmichomelessPublic

About

Background GPS run tracker with route recording, pace calculation and offline persistence.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

47 Commits

Folders and files

Repository files navigation

STRIDE

A native iOS running tracker that records GPS runs reliably, even in the background.

Swift 6 iOS 18+ License: MIT

Try it · Screenshots · Documentation

STRIDE app icon next to two screens: a run in progress with distance, pace and live route, and the details of a finished run on a map

STRIDE records a run with CoreLocation, filters out GPS noise, saves every point as it arrives and draws the route with MapKit. What sets it apart is that the persisted store, not the UI, is the source of truth for a run, so a run survives screen lock, backgrounding and a killed process. There is no public demo and no App Store or TestFlight build: you build it from source.

What it includes

  • Live tracking: start, pause, resume and finish a run with elapsed time, distance, current pace and average pace.
  • Filtered GPS: samples with poor accuracy, impossible jumps or duplicates never reach the distance or the route.
  • Background recording: a run keeps recording with the screen locked, using the location background mode only while a run is active.
  • Recovery: points are written one by one, so an interrupted run is restored as paused and can be resumed or finished.
  • History and route maps: completed runs are listed and open on a MapKit map with start and finish markers.

How to try it

Requirements: macOS with Xcode (iOS 18+ simulator) and XcodeGen. The Xcode project is generated from project.yml and is not committed.

brew install xcodegen
xcodegen generate
xcodebuild test -project STRIDE.xcodeproj -scheme STRIDE \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro'

This generates the project and runs the unit tests in the simulator; it does not install or distribute anything. To see the app, open STRIDE.xcodeproj in Xcode and run the STRIDE scheme. The simulator has no GPS, so simulate a route with Xcode's Debug → Simulate Location or the simulator's Features → Location menu. Use any installed simulator name if iPhone 17 Pro is not available.

Screenshots

Captured from the app in the iPhone 17 Pro simulator with a synthetic route in the Retiro park (Madrid). Click an image to open it at full size.

Ready to run
Tracking tab before a run: GPS ready, timer at 0:00, distance and pace placeholders and a Start button
Run in progress
Tracking tab during a run: elapsed time, distance, pace, Pause and Finish buttons and the live route on a map
Run details
Details of a finished run: route on a map with start and finish markers, distance, duration, average pace and start and end times
History
History tab listing the recorded run with its distance, duration and pace

The screenshots are in light mode only. How to recapture them and rebuild the cover is described in docs/screenshots/README.md.

Architecture

flowchart LR
    A["CoreLocation"] -->|"AsyncStream of samples, distanceFilter 5 m"| B["LocationService"]
    B -->|"accuracy, speed and duplicate checks"| C["SampleFilter"]
    C -->|"each accepted point is saved first"| D[("RunStore (SwiftData)")]
    D -->|"RunMetrics from stored points"| E["SwiftUI views"]
Loading

LocationService owns CLLocationManager, authorization and the background configuration, and turns each CLLocation into a plain LocationSample. TrackingCoordinator is created by the app, not by a view: it pushes samples through SampleFilter and the run state machine and writes every transition and accepted point to the RunStore before it counts as part of the run. SwiftUI only renders that state and forwards intents (start, pause, resume, finish), so recreating a view or relaunching the process cannot lose a run.

Decisions

Decision Why Cost
Best accuracy with a 5 m distanceFilter Keeps a usable route without one update per second; the simulation shows it halves updates compared with no filter (PERFORMANCE). Higher GPS radio cost than coarser settings; battery was not measured, and 10 m is the next candidate to test on a device.
Persisted store as the source of truth An interrupted run is recovered from disk instead of from memory. Every accepted point is a write, and recovery adds a state (restored as paused) the user must resolve.
Local SwiftData, no backend Nothing to deploy, works offline, keeps location data on the device. No sync, no backup, no sharing between devices.
Reject samples instead of smoothing them Distance can only come from points that passed explicit, testable rules (30 m accuracy, 12 m/s, 1 m duplicates). A genuinely fast or imprecise stretch is dropped, so the route can show gaps.
No keep-alive tricks, location background mode only Respects the iOS execution model instead of fighting it. If iOS kills the app, the time it was not running is a gap in the route (see BACKGROUND).
Opaque cards on the icon's gradient Text keeps the system contrast in light and dark mode. Less of the system's translucent look.

Quality

Last run on 2026-10-08, iPhone 17 Pro simulator (iOS 26.4): 165 tests in 18 suites passed in about 2 seconds. They use Swift Testing with deterministic input and an injected clock, and cover distance, pace, GPS filtering, the run state machine, permission loss, recovery after a relaunch and background configuration. A seeded simulation compares CoreLocation configurations through the real filter and metrics (PERFORMANCE).

The XCUITest in STRIDEUITests walks through a synthetic run to capture the screenshots. It is not a regression suite: it asserts only that the app reaches each screen, it takes about 2.5 minutes, and it has its own STRIDEScreenshots scheme so the default test run stays fast.

Not tested:

  • Physical device. Real GPS error, background suspension, kills by the OS and permission changes from Settings were not exercised; the simulator does not behave like iOS on hardware.
  • Battery. No measurement was made. The manual protocol in PERFORMANCE has not been run.
  • CI. No automated pipeline exists; tests are run locally.

Limits

  • No App Store, TestFlight or downloadable build; it is built from source.
  • Foreground and background recording is verified with deterministic tests, not with outdoor runs.
  • If the process is killed, STRIDE does not relaunch itself (no significant-location-change or region monitoring), so a gap appears until the user opens the app.
  • Finished runs can be saved to Apple Health (write only: distance, duration and route; see docs/HEALTH.md). Out of scope: social features, Apple Watch, reading Health data, heart-rate sensors, third-party integrations and any backend (PRODUCT).
  • Screenshots are in English and light mode only; the interface itself is localized (see docs/LOCALIZATION.md).

Documentation

Product · Architecture · Data model · Location architecture · GPS filtering · Background tracking · Reliability · Maps · Metrics · History · Design · Performance · Screenshots · Demo · Release notes

License

MIT © 2026 David Rodríguez

About

Background GPS run tracker with route recording, pace calculation and offline persistence.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages