An offline-first fishing companion for iOS.
Built in Swift, designed for anglers who fish where the cell signal doesn't reach.
Features · Architecture · Build · Roadmap · License
Currents is a fully local, offline-first fishing app: log catches, analyse gear, track spots, forecast the bite, and identify species on-device. Nothing leaves your phone unless you choose to share it. No accounts, no tracking, no cloud round-trips.
The app is written in Swift 5.10 / SwiftUI, targeting iOS 26 with the Liquid Glass design language, and persists everything through GRDB.swift on SQLite. Weather data is fetched from the free Open-Meteo API when online and cached aggressively for offline use. Fish species identification runs on-device via CoreML.
- Pin-drop locations — pick a spot anywhere on the map, not just your current GPS fix.
- Photo capture with inline species suggestion from the on-device classifier.
- Metadata that matters — weight, length, water temperature, gear used, notes, forecast score at the moment of capture.
- Private by default — nothing leaves the device. A per-catch privacy radius obfuscates shared coordinates.
The ForecastEngine computes a 0–100 bite score from a weighted combination of factors that actually predict fish behavior:
| Factor | Weight | Source |
|---|---|---|
| Barometric pressure trend | 15 | Open-Meteo hourly pressure |
| Solunar major / minor windows | 15 | On-device astro calc (no API) |
| Tide phase | 15 | Simplified harmonic prediction |
| Time of day (golden hours) | 15 | Sunrise / sunset from location |
| Pressure level | 10 | Open-Meteo current |
| Moon phase | 10 | Synodic cycle from 1999-12-25 new moon |
| Wind | 8 | Open-Meteo current |
| Water temp vs species optimum | 7 | Bundled species table |
| Spawning zone activity | 5 | Bundled seed data |
Each factor is normalised 0–1 and combined additively, so the same engine works for freshwater bass and saltwater kingfish just by swapping the species profile.
Tap anywhere on the map and Currents shows you why that spot is (or isn't) worth a cast: live weather, a full bite-score breakdown, probable fishing spots ranked by local catch history, and nearby saved waypoints. Save the tap as a new spot in one button press.
Apple Maps as the default renderer, with bathymetry-aware tile overlays where available. Downloadable PMTiles regions for air-gapped use are wired through MapManager.
A CoreML classifier runs inference locally — no images leave the device. See docs/ml.md for the model pipeline and how to swap in your own trained weights.
- Personal bests per species
- Monthly catch trend
- Best hours (heatmap derived from your own catch history)
- Gear effectiveness — which rig caught which species most often
- Spot productivity ranking
┌──────────────────────────────────────────┐
│ SwiftUI Views (Features/*) │
└──────────────┬───────────────────────────┘
│
┌──────────────▼───────────────────────────┐
│ ViewModels / @Observable AppState │
└──────────────┬───────────────────────────┘
│
┌──────────────▼───────────────────────────┐
│ Repositories (Core/DB/Repositories/*) │
└──────────────┬───────────────────────────┘
│
┌──────────────▼───────────────────────────┐
│ GRDB.swift → SQLite │
└──────────────────────────────────────────┘
Cross-cutting services
─────────────────────
WeatherService (actor, Open-Meteo cache)
ForecastEngine (pure value type, deterministic)
SolunarEngine (astro, no network)
TideEngine (harmonic prediction)
FishClassifier (actor, CoreML + Vision)
LocationManager (CoreLocation wrapper)
MapManager (MapKit + PMTiles regions)
- Every screen reads from local SQLite. No network calls on the UI thread, ever.
- Every write goes to local SQLite first. The user sees instant success; sync happens later (when a sync layer exists).
- Pure functions for anything forecast-related.
ForecastEngineandSolunarEnginetake values in, return values out. Trivial to test, deterministic, no hidden state. - Actors for anything async.
WeatherServiceandFishClassifierare actors so concurrent callers serialise cleanly without locks. - Seed data is compiled in, not bundled as resources. Apple's resource bundling has historically been flaky for Swift Package Manager targets and non-trivial Xcode configurations — embedding JSON as Swift string literals removes a whole class of "works locally, crashes in CI" bugs. See
Core/DB/SeedData/.
See docs/architecture.md for the full breakdown.
currents/
├── ios/ Xcode project (generated via XcodeGen)
│ ├── Currents/
│ │ ├── App/ @main, AppState, Info.plist
│ │ ├── Core/ DB, Weather, ML, Astro, Maps, Theme
│ │ ├── Features/ Map, Catch, Forecast, Gear, Profile, Trip, Species
│ │ └── Resources/ Assets.xcassets, Data/
│ ├── Tests/ XCTest unit tests
│ └── project.yml XcodeGen spec
├── ml/ Model training + CoreML conversion
│ ├── train.py
│ ├── convert_coreml.py
│ └── README.md
├── scripts/ Seed-data generators (Python → Swift)
├── docs/ Architecture, data model, ML notes
├── .github/workflows/ iOS build + IPA artifact CI
├── Makefile Common commands
└── README.md You are here
- macOS with Xcode 16 or newer
- XcodeGen (
brew install xcodegen) - iOS 26 simulator runtime
cd ios
xcodegen generate
open Currents.xcodeprojOr from the command line:
make iosEvery push to master builds an unsigned .ipa via GitHub Actions (.github/workflows/ios.yml). The binary is fakesigned with ldid using the entitlements in ios/Currents/App/Currents.entitlements, which makes it compatible with sideloading tools like Sideloader and TrollStore. Download the Currents-IPA artifact from any green build and install it on your device — no paid Apple Developer account required.
- Catch logging with pin-drop locations, photos, gear
- Bite forecast engine (pressure / solunar / tide / weather)
- Location inspector (tap-to-analyse anywhere on the map)
- Personal bests, trip logging, species guide
- Unsigned IPA builds via CI
- On-device CoreML fish classifier shipped with the app
- PMTiles bundled bathymetry regions
- Optional self-hostable sync layer (PowerSync)
- Social features (opt-in public catches with obfuscated locations)
Issues and PRs welcome. See docs/contributing.md for the commit-message conventions, test expectations, and the preferred development loop.
MIT — see LICENSE.
