Sea companion · iPhone · Apple Watch · in development
SeaStar
A free, native iPhone and Apple Watch companion for snorkelling, freediving and scuba, with offline logbooks and depth profiles.
- Swift
- SwiftUI
- Swift Charts
- HealthKit
The write-up
README
view on GitHub ↗
A free, native iPhone and Apple Watch sea companion in development. Built with SwiftUI and Apple Charts; no third-party runtime dependencies, accounts, ads or subscriptions. Local recording and logbooks work offline; forecasts and map tiles use network services.
Run
Open SeaStar.xcodeproj in Xcode. Select the SeaStar scheme and an iPhone simulator, or SeaStarWatch and an Apple Watch simulator, then Run. Requires Xcode with iOS 17+ / watchOS 10+ SDKs. On physical devices, replace DEVELOPMENT_TEAM in project.yml with your own team and change bundleIdPrefix to a namespace you control. The phone app embeds the Watch app.
The generated project is included. If changing project configuration, edit project.yml and run xcodegen generate (XcodeGen is only a development tool).
Try it
- Choose Scuba, Freedive, or Snorkel and edit the site name.
- Start a demo. The two-minute simulation descends, holds depth, and returns to the surface. Pause/resume or finish early.
- Finish and save, open Logbook, inspect the depth profile, and save a note. Logs survive relaunch.
- On Watch, pick Snorkel, Freedive or Scuba with the switcher, start a demo, and use the Crown or swipe to reach the controls page. Finish and save to its local logbook.
All three activities use illustrative profiles, not physiological models. Scuba tops out at 18.4 m, Freedive at 12 m, Snorkel at 3.5 m. Water temperature is a fixed demo value of 24°C. The empty phone logbook includes a clearly labeled sample dive; it does not count toward your totals.
Activity-aware Watch session
Snorkel, Freedive and Scuba are chosen with the switcher at the top of the Watch home screen; each carries its own colour, symbol and depth scale through every later screen. The live session opens on a single-screen glance, and the instrument on it follows the question that activity actually asks.
Snorkel and Scuba read depth, so they get a vertical depth gauge scaled to the activity (6 m for snorkel, 40 m for scuba) with graduations and a marker at the deepest point reached; the colour maps to absolute depth, so a given shade always means the same depth on that scale. Freedive reads time, so it gets a breath-hold ring around the hold clock, with the current depth beneath it. The ring fills towards the longest hold already completed in that session; before there is one to compare against, it sweeps the minute. Surfacing switches the ring to the surface interval and the eyebrow to SURFACE, and the next descent increments the dive number.
The three supporting values differ too: snorkel shows max depth, water temperature and heart rate; scuba shows max depth, vertical rate and heart rate; freedive shows the dive count, hold maximum and last hold. The complete sensor readout is the next page up, so the glance repeats nothing. Instrument and numeral sizes are derived from the display, so the glance holds one screen from a 40 mm Watch to an Ultra.
Personal depth and time alerts remain personal reminders. No safe depth, ascent-rate or breath-hold limit is implied, and no scuba guidance is given.
Liquid Glass
On watchOS 26 and iOS 26 the chrome is drawn with Liquid Glass: the activity pill, the session chips and readouts, the floating session bar, the primary action, and the empty part of the depth gauge, which reads as a glass tube with the water sitting in it. Choosing an activity flows one piece of glass across to it and retints it, rather than jumping a filled block.
Glass needs something behind it to refract, and this app's background was near-black, so the screens that use it now sit in the chosen activity's water: a gradient, a waterline and a soft glow in that activity's colour. The live session uses a much quieter version of the same backdrop, because depth and time have to stay readable through a mask.
Earlier systems keep the previous materials, and Reduce Transparency or increased contrast replaces glass with the solid panel fill on every system. Both paths are in the code; only the glass path has been looked at on screen.
Health connection
The Connect tab on iPhone and Apple Health link on Watch request read permission for heart rate, underwater depth, water temperature, active energy, swim distance and stroke count through HealthKit. Latest available records include their timestamps. Empty results do not imply denial: HealthKit deliberately does not reveal read permission status. Simulations never write to Health. The integration builds on both platforms; permission and real-data behavior still need device verification.
iCloud and automatic Health saving
Your Apple Account is your SeaStar account. There is no SeaStar account to create, no password, and no SeaStar server: dives sync through the private CloudKit database of whichever Apple Account is signed in on the device, and nothing is readable by anyone else. Turning sync off leaves every dive on the device it was recorded on.
Sync runs on CKSyncEngine, which owns the change tokens, batching and retries. A dive travels as one JSON asset keyed by its own UUID. Arrivals are validated before they touch the logbook — right schema, within the size ceiling, decodable, valid, and carrying the identity the sender claimed — and merge through the same revision ordering as paired-device transfer, so a dive edited on two devices converges to the same winner whichever way the changes arrive. Repeated syncs do not duplicate dives, and a remote arrival never bumps the revision, so devices cannot ping-pong edits. Connect → iCloud shows status and a manual sync.
Finishing a measured session now writes it to Apple Health without being asked. Where a live workout already recorded the session, that remains the path; where none did — an iPhone session, or a Watch session saved with Health recording off — the dive is written afterwards as a workout. Scuba and freedive are recorded as underwater diving; snorkel is recorded as open-water swimming, which is what it is. Simulated dives are still never written to Health. Writing is idempotent: a dive already carrying its id in HKMetadataKeyExternalUUID is never written twice, which is also what keeps the live path and this one from duplicating.
CloudKit sync is off unless the build provides it. CKContainer(identifier:) raises rather than failing softly when its container is not provisioned, so the code refuses to touch CloudKit unless SeaStarCloudSyncEnabled is YES in a signed build whose team provisions iCloud.com.yoavperetz.seastar. In the default unprovisioned build the iCloud screen says so and stays inert. The sync and Health-archive paths compile on both platforms and their pure validation logic is covered by the model checks; neither has been run against a real iCloud container or a real Health database.
Release boundaries
This is not a validated dive computer. The app includes measured sensor recorders, local recovery files, paired-device transfer, marine forecasts and experimental dive calculations. Full-depth recording remains disabled until Apple approves and provisions the required capability. The experimental decompression model is not connected to live guidance.
Recovery files can preserve interrupted local sessions; automatic recovery of a running Health workout is not implemented. Physical-device sensor behavior, Health saves, background recording and paired delivery still need end-to-end verification. See Apple’s submersion API documentation and the implementation ledger.
Verify
./scripts/check.sh
xcodebuild -project SeaStar.xcodeproj -scheme SeaStar -destination 'generic/platform=iOS Simulator' -derivedDataPath /tmp/SeaStarBuild CODE_SIGNING_ALLOWED=NO build
The model check covers activity depth boundaries, partial-second samples, serialization, local save/reload, updating notes without duplicates, and preservation of unreadable logbooks. The phone scheme builds its embedded Watch target with the appropriate watchOS SDK. Avoid forcing -sdk iphonesimulator on the combined build, as that overrides the Watch target’s SDK too.
Paired-device sync
Connect → Device sync (iPhone), or Device sync (Watch), shows transfer status and a retry action. Saved records use UUIDs and revision numbers; stale transfers cannot overwrite newer edits. Duplicate delivery is idempotent. Simultaneous revisions resolve by edit date, then note text, then a deterministic comparison of the remaining record; the losing concurrent note is not kept as a separate version. WatchConnectivity queues files while the counterpart is unavailable. A retry reoffers the local logbook; receipt validates data and saves atomically. Delivery status confirms transport, not a remote save acknowledgment. Physical paired-device delivery remains to be verified.
Measured depth recorder
Watch → Record depth contains the Core Motion depth/pressure/temperature recorder. It stores measured sessions distinctly from simulations, uses sensor timestamps, hides depth after five seconds without a fresh reading, and keeps an atomic recovery copy. Returning to the recorder after interruption lets you save that copy. Missing temperature remains absent. The chart separates gaps longer than five seconds.
Sensor start is disabled in simulator and in the default unprovisioned build. For an approved full-depth development profile, configure the Apple-issued entitlement and set SeaStarDepthAccessEnabled to true in the signed target. Do not turn this on without appropriate provisioning. Background runtime, automatic launch and workout-writing paths are implemented, but physical-device verification remains outstanding. No scuba guidance is enabled.
Surface GPS and compass
Use Record a surface route on iPhone, or Surface route on Watch. Permission is requested on start. The recorder accepts ordered fixes with at most 50 m horizontal uncertainty and timestamps within 15 seconds of receipt. Magnetic heading is hidden after five seconds without an update. Surface air pressure is sampled from the barometer when the hardware has one. Finished routes join the logbook and paired-device transfer; the iPhone detail map separates GPS gaps over 30 seconds. GPS-only records do not fabricate depth samples. An interrupted route has a recovery copy. Active recording continues if you leave the screen; iPhone uses the system location indicator while a session is running. Physical accuracy still requires device verification. Map tiles depend on Apple Maps connectivity and caching.
Watch workouts and live heart rate
Before starting a Watch surface route, enable Save swim workout to Health. SeaStar requests workout/heart-rate/energy permissions and starts an outdoor open-water swimming workout. Live heart-rate readings expire after 15 seconds without updates. Finishing ends the workout and asks HealthKit to save it; errors are shown separately from the local logbook. The local route carries measured heart-rate samples and the iPhone detail shows their sample average. Workout-processing is configured for inactive-display runtime, pending physical-device verification. Leaving the recording view does not end an active session. Interrupted Health sessions are not yet recovered into a running HKWorkoutSession.
Combined Watch water session
Watch → Record water session uses the selected Scuba/Freedive/Snorkel activity. Surface GPS and Health workout switches control optional capture; one measured log record carries depth, pressure, temperature, route fixes, heart rate and surface air pressure under a common start time. GPS fixes may be absent underwater. Starting a session enables Water Lock and can continue after you leave the screen. Arm auto-start to begin when the Watch reports submersion (approved depth entitlement required). The Action button can launch an underwater-diving or swim workout. One recovery file preserves the combined record and one save queues it to the iPhone. Full-depth access still requires approved provisioning. The Health workout is classified as underwater diving; no decompression advice is supplied. Device execution remains unverified.
Connect → Sensors (iPhone and Watch) reads live GPS, magnetic heading and barometer on the device in your hand without saving a route. Underwater depth and water temperature are Watch Ultra water-session sensors. Connect → Units switches metric and imperial for live and logged depth, temperature and air pressure.
The iPhone Dive tab’s primary action is Start live session: GPS, compass, barometer and optional Health heart rate. Drag down to dismiss before recording starts (rubber-bands at the top, then springs with the gesture’s predicted end). Underwater depth is recorded on Apple Watch Ultra via Start live water session.
Measured Health export
Finishing a Health-enabled Watch session now adds measured depth and temperature samples and the GPS route to the workout. Depth/temperature write permission is requested for underwater-diving workouts; route write permission is requested for both workout types. Original temperature timestamps are retained and repeated readings deduplicated. Denied categories are omitted with a partial-save status; local records remain available. This path is compiled and covered by pure-data export checks, but has not been verified against a physical device’s Health database.
Backup, restore and sharing
The logbook toolbar opens Backup & sharing; individual session details also offer export. JSON session archives preserve all session fields. Photo/video files are stored separately and are not included; planning has its own archive. Sharing copies can omit GPS/site name, heart-rate history, and notes; these fields are omitted by default, while date/activity/depth/temperature remain. Sharing copies receive new IDs so they cannot overwrite private originals. Native file export leaves destination selection to you.
Restore previews the session count and merges only after you tap Import. Invalid versions, invalid records and duplicate IDs are rejected before writing. Existing newer revisions remain. Limits are 64 MB per archive and 10,000 sessions per logbook. Merge is atomic; import does not delete existing sessions or overwrite an unreadable local file.
Planning
The iPhone Plan tab stores dive sites (optional coordinates, favorites and notes), buddy contacts, and equipment with persistent packed state. Select a site for the preparation screen. Site names update when edited, and equipment progress links back to the checklist. The site map uses only coordinates you enter; the app does not invent site locations. Planning data is local to the iPhone and has a separate planning archive; it is not yet included in Watch sync. Open Plan → Planning backup, or Logbook → Backup & sharing → Back up sites, buddies & equipment. Export preserves coordinates, favorites, contacts, notes and packing state. Restore previews new and matching entries; matching IDs retain their current details unless you enable Replace matching entries. Import is atomic, accepts at most 64 MB and 2,000 items per category, and preserves unreadable original files. Map tiles may need connectivity; saved details remain offline.
Photos and videos
Saved iPhone dive memories now include a Photos & videos gallery. Use Add photos or videos to select up to ten items at a time through the system Photos picker. SeaStar copies selected files into its local media library, checks image/video format, and preserves the original in Photos. Photos are limited to 64 MB and videos to 512 MB, with up to 200 attachments per dive. File loading and thumbnail generation run away from the main UI thread. Apple's file-transfer representation avoids loading whole movies into memory.
Tap an attachment for the native zoom/playback preview and original-file sharing. Original files can retain embedded location metadata. Long-press for captions or removal of SeaStar's local copy. The gallery is local to this iPhone; files are not part of the JSON logbook archive or Watch transfer. Portable media backup, color correction and supported underwater-housing capture remain outstanding.
Image and actual H.264 video fixtures verify local copy/reload, independent dive associations, captions, invalid formats, oversized files, symbolic-link rejection, failed-import cleanup, removal and preservation of a corrupt media index. The iPhone/Watch simulator build passes. Photos-picker interaction, preview playback and sharing still require UI/device verification.
Dive buddies, equipment and ballast
Open an iPhone logbook session → Add dive setup. Choose saved buddies and equipment, and optionally record the ballast weight you used in kilograms or pounds. This is a log entry, not a weight recommendation. Names are copied into the session so later planning edits do not rewrite dive history; contacts and private planning notes are not copied. The setup travels in the existing session transfer and archive formats and is displayed in the Watch logbook. Old sessions without setup remain valid.
Saving notes updates only notes; saving setup updates only setup on the latest stored session. Equal-time conflicts resolve consistently regardless of delivery order. Sharing copies omit setup unless Include buddies, equipment & ballast is enabled. Local persistence, privacy redaction, payload serialization and conflict checks pass; physical paired delivery and editor interaction still need verification.
Marine forecasts
Saved sites with coordinates offer an on-demand three-day marine forecast from Open-Meteo. The request sends only coordinates and forecast parameters. Hourly waves, period, sea-surface temperature and modeled mean-sea-level height are displayed with units and device-local timestamps; nulls remain unavailable. Results cache locally, show retrieval time, and warn after six hours. Sea level includes tides and other effects but is not chart datum or navigation data. No dive-safety recommendation is inferred.
The open-access endpoint is for non-commercial use with provider limits; commercial distribution requires a suitable service license or deployment. Free-to-users pricing does not itself settle that requirement. The app is not commercially deployed. A request using the provider’s public sample coordinates returned 72 hours and was decoded successfully.
Experimental dive computation
Connect → Computation lab exercises a nitrogen tissue model and no-stop search with gas settings. It is research output only and is not available as live dive guidance. Model notes document the source table, equations, assumptions, mathematical checks and unfinished validation. The current model is not a validated replacement for a dive computer.