hopp til innhold
Fjærdinghage
PaTur-main · gjennomgang av kodebasen

På tur & Tintin: hele appen, fra klokke til kart

En SwiftUI-app i tre mål — iPhone, Apple Watch og en Live Activity — bygget rundt én idé: klokka eier turen, telefonen forteller historien om den. Under er arkitekturen, funksjonaliteten, designet og brukerscenarioene, slik de faktisk står i koden i versjon 0.3 (build 17).

Språk
Swift · SwiftUI
Omfang
34 filer · 10 360 linjer
Mål
3 (iOS, watchOS, widget)
Versjon
0.3 (17)
Min. OS
iOS 26 · watchOS
Region
nb — norsk først
01

Hva prosjektet er

«På tur» er en personlig treningsapp for turer med hunden Tintin. Den er ikke en generisk løpeapp med hundetema — hele datamodellen, statistikken og til og med kjøringsdeteksjonen er formet av hva en hundetur faktisk er: uforutsigbar, oppdelt i etapper, full av snusepauser, og av og til avbrutt av en biltur til turområdet.

Prosjektet startet 22. august 2026 som en 22-minutters MVP generert i en kodeagent-økt (skjermbildene i Tidslinje_og_Logg/ dokumenterer kjøringen), og har siden vokst betydelig: GPX-import, live navigasjon, flerarts-etapper, kjøringsdeteksjon, utforskningskart, Live Activity og et eget diagnoseverktøy for HealthKit-ruter er alt kommet etterpå.

Appen distribueres via TestFlight (fire ExportOptions-varianter ligger i repoet), og AGENTS.md fastsetter arbeidsflyten: brukervendt navn er alltid «På tur», tekniske identifikatorer er PaTur, og CURRENT_PROJECT_VERSION skal bumpes for hver TestFlight-build mens MARKETING_VERSION bare endres på faktiske releaser.

Den bærende arkitekturbeslutningen

Apple Watch er eneste eier av en pågående tur. Telefonen muterer aldri treningstilstand — den sender kommandoer med UUID og venter på at klokka bekrefter dem tilbake i neste snapshot. Det gir én skriver, ingen flettekonflikter, og en telefonapp som kan miste kontakten uten at turen påvirkes.

02

Arkitektur

Xcode-prosjektet (objectVersion 71, Xcode 27) bruker PBXFileSystemSynchronizedRootGroup — mappene er byggekonfigurasjonen, det finnes ingen manuelle fillister. Tre mål deler kode gjennom to bevisst atskilte «shared»-mapper.

MålBundle IDKildemapperRolle
På turcom.ding.paturiOS + Shared + LiveActivitySharedArkiv, statistikk, rutebibliotek, speiling av klokka
På tur Watch Appcom.ding.patur.watchkitappWatch + SharedSelve opptaket: HealthKit, GPS, etapper
PaTurLiveActivitycom.ding.patur.liveactivityLiveActivity + LiveActivitySharedLåseskjerm og Dynamic Island

Shared/ går til telefon og klokke, men ikke til widgeten. Det er derfor WalkActivityAttributes bruker rå String for aktivitet og fase i stedet for enum-typene: widget-utvidelsen skal ikke trenge domenemodellen. Prisen er at koblingen bare håndheves av strengliteralen phase == "paused".

APPLE WATCH — EIER HKWorkoutSession HKLiveWorkoutBuilder HKWorkoutRouteBuilder CLLocationManager DrivingDetectionEngine HEALTHKIT Workouts + ruter com.ding.patur.* routeSchema 3 IPHONE — SPEIL WalkStore (JSON) PlannedRouteStore MirrorManager HealthRecovery LIVE ACTIVITY ActivityKit Dynamic Island APPLE MOSJON leser ruten fra Helse lagrer snapshot 2 s kommando gjenoppretting WatchConnectivity: "walk" ← klokke · "plannedRouteLibrary" → klokke
Turen går tre veier ut av klokka samtidig: til HealthKit som en ekte trening, til telefonen som live-snapshot over HealthKit-speilingen, og som ferdig TintinWalk over WatchConnectivity. Telefonen har i tillegg en fjerde vei inn — den kan rekonstruere tapte turer direkte fra Helse.

Tre uavhengige overføringskanaler

Redundansen er ikke tilfeldig. Hver kanal dekker en annen feilmodus:

  • HealthKit workout mirroring (sendToRemoteWorkoutSession) bærer live-trafikken: snapshot hvert 2. sekund, rutepunkter i biter på 120, kommandoer med UUID-kvittering og de-duplisering av de siste 32 ID-ene. Speilingen forsøkes gjenopprettet maks hvert 20. sekund, og en død speiling påvirker aldri selve turen.
  • WatchConnectivity bærer det holdbare: ferdig tur som transferUserInfo(["walk": Data]) fra klokka, og rutebiblioteket motsatt vei som applicationContext + sendMessage. Rutevalg er bevisst utelatt fra nyttelasten — klokka velger rute selv, uten telefonen.
  • HealthKit som sannhetskilde: HealthRecoveryCoordinator kjører ved oppstart og hver gang appen blir aktiv, spør etter alt merket com.ding.patur.tintin pluss alt fra egen bundle-ID de siste 90 dagene, og rekonstruerer turer som aldri kom over.

Lagring

Ingen Core Data, ingen SwiftData, ingen iCloud. Alt er ObservableObject + @Published + @EnvironmentObject, og persistens er atomiske JSON-filer.

DataStedFormat
TurerApplication Support/PaTur/walks.jsonJSON, atomisk skriving
Planlagte ruter…/planned-routes.jsonJSON, atomisk skriving
HundeprofilUserDefaults["dogProfile"]JSON
Aktiv økt på klokkaUserDefaults activeTintin*7 nøkler for krasjgjenoppretting
Kartlag, turnumre@AppStorageString / Bool
Turnumre («Tur 12»)—Aldri lagret, alltid utledet

Det siste punktet er en fin detalj: det synlige turnummeret regnes ut fra kronologi hver gang, med id.uuidString som deterministisk tie-break. Sletter du tur 4, lukkes hullet — men ingen permanent ID i HealthKit eller statistikken flytter seg.

03

Datamodellen: turen som etapper

Kjernen er TintinWalk — en hel utflukt med permanent UUID — som inneholder én eller flere kronologiske TintinTripSegment-etapper, hver med sin egen permanente UUID. En etappe er gåtur, løpetur, biltur eller pause. En vanlig énaktivitetstur lagres som én tur med én etappe, og gamle poster migreres automatisk til samme form.

TintinWalk holder samtidig en denormalisert oppsummering på toppnivå (total distanse, sluttid, hele ruten) for det eldre enkeltaktivitets-brukergrensesnittet. segments er private(set) — tidslinjen er autoritativ, oppsummeringen er avledet.

Avledningene som er verdt å merke seg

  • Varighet er summen av etappene, ikke endedAt − startedAt. Dødtid mens HealthKit lagrer mellom to etapper telles bevisst ikke — ellers ville hver aktivitetsbytte blåst opp turtiden.
  • Snittpuls er et varighetsvektet gjennomsnitt over etappene som faktisk har puls, med 1-sekunds gulv per vekt.
  • Distanse for hundesteg bruker pedestrianDistanceMeters — bare gå- og løpeetapper. En biltur skal ikke gi Tintin-steg.
  • Aktivitetstittel følger norsk listegrammatikk: «Gåtur», «Gåtur og løpetur», «Gåtur, Løpetur og biltur».

Innstillingene fryses ved opptak: steglengde, snusefaktor og hundenavn kopieres inn i hver tur, så en senere justering i profilen endrer ikke historiske estimat. Codable er håndskrevet med skjemaversjon, og alle valgfrie nøkler har defaults ("Tintin", 0,32 m, 1,12) slik at gamle filer alltid leses.

Tintin-steg

estimatedDogSteps = pedestrianDistanceMeters × wanderingFactor ÷ dogStepLengthMeters

Standard: 0,32 m steglengde (justerbar 18–60 cm) og 1,12 i «snusefaktor» (justerbar 1,00–1,50), altså +12 % for at Tintin beveger seg mer enn GPS-sporet til mennesket. En 3 km gåtur blir ≈ 10 500 hundesteg. Appen kaller det selv «et lekent estimat, ikke en helsemåling».

04

Apple Watch: der turen skjer

WatchWorkoutManager er prosjektets tyngste fil (1 730 linjer) og inneholder hele opptaksmotoren. Tilstandsmaskinen er ready → requesting → active ⇄ paused → finishing → finished | failed.

Én HealthKit-økt per etappe

Dette er det mest uvanlige designvalget: hver pause, hvert aktivitetsbytte og hver avslutning avslutter HealthKit-økten og starter en ny. Det gir separate, korrekt typede treninger i Helse (gåturen som gåtur, løpeturen som løpetur) mens «På tur» holder dem sammen gjennom felles tripID i metadataene. Pauser lagres som egne .pause-etapper. Bilturer er rene GPS-etapper uten HealthKit i det hele tatt.

Metadata skrevet på hver trening

HKMetadataKeyWorkoutBrandName = "På tur", IndoorWorkout = false, og et eget navnerom: com.ding.patur.tintin, .routeSchema = 3, .tripID, .walkID (legacy-alias), .segmentID, .segmentKind, .activity, .recoveredSession. Rutesamplet får i tillegg punkttelling, avviste punkter, største råluke og maks nøyaktighet — telemetri som diagnoseverktøyet senere leser.

GPS-håndtering

kCLLocationAccuracyBest, distanceFilter = 2, headingFilter = 2, activityType = .fitness (byttes til .automotiveNavigation for bilturer). Punkter forkastes utenfor 0–50 m nøyaktighet eller utenfor etappens tidsvindu.

GPS-lås krever minst 3 av de 5 siste kandidatene, beste nøyaktighet ≤ 12 m, og at de tre siste ligger innenfor 15 m av den beste. Før det får du «Venter på GPS» i stedet for en tilfeldig strek over kartet.

Skjermene

I tur vises tre sider i en TabView: Kontroller → Måltall → Kart.

Klar start

«Gå med Tintin», «Løpe med Tintin», «Kjøre med Tintin» — og «Velg rute» med navn og distanse på valgt GPX-rute.

Måltall

Varighet i 43 pt, distanse, og puls — eller bilikon og «BILTUR» i turkis når etappen er en biltur.

Kontroller

Pause/Fortsett og Fullfør øverst; deretter «Bytt til …», «Marker», «Rute» og «Mål» (destinasjonssøk).

Kart

Planlagt rute, navigasjonsrute i cyan, eget spor i oransje, potemarkør med retningskjegle. Tre kameramoduser, 420 m kamerahøyde.

Ferdig

«Gåturen er lagret», «≈ N Tintin-steg 🐾», og en advarsel hvis ruten ikke nådde Helse.

Feilet

«Noe gikk galt» med konkret melding og «Prøv igjen» — ingen tapt tilstand, økten kan gjenopprettes.

Haptikk brukes gjennomgående og med mening: .click ved valg, .success ved markering, .failure når GPS mangler, .notification når kjøring oppdages.

Krasjgjenoppretting

recoverActiveWorkoutSession kalles fra init, ved applicationDidBecomeActive og i egen håndterer. Bygger, datakilde og rutebygger kobles på igjen, og tur-ID, etappe-ID, starttider og ferdige etapper leses tilbake fra UserDefaults. Den gjenopprettede økten merkes med recoveredSession: true.

Live Activity

Låseskjermen viser potemerke, «Gåtur med Tintin», «Live fra Apple Watch» og tid + distanse. I Dynamic Island: potepote og kort distanse kompakt, tid og puls utvidet. Tidsvisningen bruker Text(timerInterval:) så den tikker selv uten oppdateringer. Oppdateringer sendes bare ved faseendring, ≥ 10 m distanseendring, ≥ 4 slag pulsendring, eller etter 8 sekunder — med staleDate på 15 s og avvisning 30 s etter slutt.

05

iPhone: arkivet og analysen

Fire faner, hver med egen NavigationStack.

FaneTittelInnhold
På turPå turLive-kort fra klokka med fjernkontroll, siste tur med kart, hundeprofil i verktøylinjen
TurerTurene våreKronologisk liste, sveip for å slette, «Fjern eksempel»
RuterPlanlagte ruterGPX-import, høydeprofil, «Kjør til start», offline-status for klokka
OversiktVåre eventyrUtforskningskart, filtre, søylediagram, siste etapper

Live-speilet

Når klokka starter en tur dukker den opp av seg selv på telefonen. Kortet ekstrapolerer forløpt tid mellom snapshotene, viser statusprikk (oransje = live, gul = kobler til igjen), og gir fjernkontroll: pause, marker sted, bytt gå/løp, fullfør — hver med sin egen «Setter turen på pause …»-tilstand til klokka kvitterer. Kartet kan følge kompassretningen, med kurs beregnet som storsirkelretning fra det siste punktet minst 10 m bak.

Turdetaljer

Kart med fargekodede etapper (gåtur oransje, løpetur blå, biltur turkis, pause grå), åtte måltall i rutenett, en etappe-tidslinje med klokkeslett, varighet, distanse og tempo per etappe, «Lagre som fast rute», hundesteg-kort og «Turminne» — et fritekstnotat som skrives til disk for hvert tastetrykk.

Utforskningskartet

Den mest ambisiøse iOS-skjermen (1 230 linjer) og direkte svar på ønsket om å «se hvor vi går oftest». Alle turer tegnes samtidig, fargelagt etter ukedag — mandag blå, lørdag rosa, søndag lilla — med filtre for ukedag/hverdag/helg, periode (30 dager, i år, alle år), aktivitetstype og tre kartlag (Minimal, Standard, Satellitt).

Tre detaljer gjør det brukbart i praksis: turnummer-etikettene plasseres 55 % inn i ruten (ikke ved start, der alle turer overlapper), de klustres i tre zoomnivåer (4×4-rutenett med maks 8 grupper langt ute, opptil 18×18 med 40 grupper nært), og trykk treffer nærmeste polylinje med toleranse skalert etter zoom — ligger flere turer oppå hverandre får du dialogen «Velg tur».

Rutebiblioteket

GPX importeres med XMLParser (med shouldResolveExternalEntities = false mot XXE), tynnes til 2 m mellom punkter og maks 5 000 punkter, og parses på en detached task. Rutedetaljen viser høydeprofil som Swift Charts-areal, «Kjør til start» via Apple Kart med kjøreanvisning og trafikk, og et offline-kort. Eksempelfilen i repoet — Ramstad Terrasse 15 til Langhus stasjon, 134 punkter — er en planlagt rute uten tidsstempler.

Budsjettet mot klokka

Hele GPX-en blir på telefonen. Til klokka sendes maks 560 punkter totalt, fordelt likt (2–180 per rute), og hvis JSON-en likevel passerer 48 000 byte krympes den i runder på 0,72 til punkttallet treffer gulvet på 12.

Statistikk

Kategori (Alle / Gå / Løp / Bil) × periode (Måned / I år / Alle) med månedstepper. I «Alle» aggregeres hele turer; ellers aggregeres etapper, så «utflukter med løping» og «separate etapper» rapporteres hver for seg. Snittempo krever minst 10 m distanse; for bil vises snittfart i km/t i stedet.

06

Fire signaturmekanismer

Dette er delene som ikke finnes i andre treningsapper, og som forklarer hvorfor kodebasen er så stor for en «MVP».

1 · Kjøringsdeteksjon med tilbakedatert kutt

Problemet: du går tur, setter deg i bilen, og turen fortsetter å registrere 60 km/t som «gåing». DrivingDetectionEngine er en liten, plattformuavhengig verdi-type med tre terskler: 8,5 m/s (≈ 31 km/t, trygt over løpshastighet), 12 sekunder vedvarende på GPS-fart alene, eller 8 sekunder hvis CoreMotion samtidig melder automotive. Ett raskt punkt utløser aldri noe, og et enkelt saktepunkt nullstiller hele timeren.

Det elegante er cutoff: den peker på siste tidspunkt du plausibelt gikk, ikke da mistanken startet. Når kjøring bekreftes trunkeres gåetappen dit, så akselerasjonen fra ganghastighet til bilfart aldri havner i gådataene. Punktene i mellomtiden holdes i karantene — ikke forkastet — så de kan spilles tilbake hvis mistanken var feil.

Deretter får du tre valg, både på klokka og telefonen: Avslutt, Biltur (karantenen blir en .driving-etappe) eller Fortsett. Merk at klokka alltid sender automotive: false — bare iPhone har bevegelsesklassifisering, og bare iPhone kan derfor bekrefte på 8 sekunder.

2 · Rutefortetting for Apple Mosjon

Apple Mosjon avviser ruter med tidsluker. Derfor genererer klokka syntetiske punkter med 2 sekunders mellomrom gjennom hele økten — interpolert mellom faktiske GPS-fikser, og gjentatt der du står stille — pluss «pre-roll» fra etappestart og «post-roll» til etappeslutt. Punktene skrives i batcher på 150 gjennom insertRouteData, med flush hvert 12. sekund eller ved 10 køede jobber. Innkommende punkter avvises hvis nøyaktigheten er over 50 m eller distansen overgår max(60, tid × maksfart + nøyaktighet), med maksfart 12 m/s løping og 7 m/s gåing.

routeSchema = 3 markerer at ruten er skrevet av denne fortettende versjonen — informasjon RouteDiagnostics bruker for å vite om tidsluker er en feil eller bare gammel modell.

3 · Kartdiagnostikk

Et helt verktøy bygget for å feilsøke Apples egne apper. Det leser siste «På tur»-økt fra Helse og rapporterer punkter utenfor økten, nøyaktighet over 50 m, ugyldige koordinater, største tidsluke, luker over 3,25 s, duplikate tidsstempler og hopp over 15 m/s — og gir en konkret konklusjon, for eksempel: «Den nye rutemodellen skulle ha fylt pausene, men ruten har fortsatt tidsluker.»

Det kan også lage en syntetisk testtur: to minutter, 61 punkter med 2 sekunders mellomrom på en sirkel med 25 m radius, der punkt 16–45 står helt stille et helt minutt — nettopp for å teste om Mosjon aksepterer gjentatte stillestående punkter. Testturer merkes routeTest: true og kan slettes samlet.

4 · Idempotent kommandoprotokoll

Seks kommandoer: pause, resume, finish, markPlace, switchToWalking, switchToRunning. Alle er absolutte, ingen er en toggle — en dobbeltlevert pause på en pauset tur er en no-op, mens en dobbeltlevert toggle ville ødelagt tilstanden. Hver kommando har en UUID, klokka ekker den tilbake i acknowledgedCommandID sammen med en norsk melding, og telefonen slutter først da å vise spinneren. Ingen egen svarmeldingstype er nødvendig.

Tallene, samlet

MekanismeTerskelHvorfor
Kjøring bekreftes8,5 m/s i 12 s (8 s m/CoreMotion)Over all løpsfart; krever varighet, ikke enkeltpunkt
GPS-punkt godtas≤ 50 m nøyaktighetFiltrerer bytunnel-fikser
GPS-lås3 av 5 · ≤ 12 m · innen 15 mUnngår villstrek ved turstart
Rutefortetting2 s mellomrom, batch 150Mosjon avviser tidsluker
Snapshot til telefonhvert 2. s, 120 punkter per bitNok til live-UI, lite nok for kanalen
Live Activity10 m / 4 bpm / 8 sSparer budsjett for oppdateringer
Rutebibliotek til klokka560 punkter / 48 kBWatchConnectivity-nyttelast
Duplikat-tur±15 s start, ±90 s sluttSamme tur via WC og Helse skal flettes
Rutegjenbruk2,5 m tynning, maks 5 000 punkterFjerner stillestående jitter
Tempo beregnes≥ 10 m distanseEllers absurde min/km-verdier
07

Design og merkevare

Systemet er strengt: én aksentfarge, og et semantisk par som snus i mørk modus. TintinPrimary og TintinCanvas er eksakte inverser av hverandre, så en visning kan skrives én gang og snu seg selv. Oransjen holdes konstant i begge utseender for gjenkjennelse, med TintinOnOrange som garantert lesbar forgrunn.

#E56B00TintinOrange · også aksent
#FAF7F2Canvas lys / Primary mørk
#17120FPrimary lys / OnOrange mørk
#050403Canvas mørk

I tillegg finnes et konsekvent fargespråk for etapper, gjentatt i tre visninger: gåtur oransje, løpetur blå, biltur turkis, pause grå. Utforskningskartet har sitt eget sett med syv ukedagsfarger.

Potemerket

PawBadge er hele appens visuelle signatur: pawprint.fill i en sirkel med automatisk gradient, der glyfen alltid er 0,46 × diameteren. Samme komponent brukes fra 34 pt i kartannotasjoner til 58 pt i hjemmeskjermens hero. Den er accessibilityHidden — rent dekorativ.

Appikonet

Ikonet bygges av et Swift-skript (Design/build_app_icons.swift) som komponerer en gradient-PNG med en vektor-SVG av en person som går med hund, skalert 1,20 så motivet blør 20 % utenfor kanten. To render: 1024 px for iPhone, 1088 px for Watch.

Design/AppIcon.svg er det opprinnelige ikonet fra første MVP — grønn gradient (#426F4B → #123C2B), kremhvit pote, terrakotta-smil — synlig i skjermbildet fra 22. august. Det er nå foreldet i forhold til byggeskriptet.

Språk som designmateriale

Alt er norsk, og tonen er gjennomført varm og konkret: «Ut på eventyr?», «Turene våre», «Våre eventyr», «Turminne», «Fint sted», «Husk dette», «Rolig runde med en lang snusepause ved vannet». Feilmeldinger forklarer: «Turen har ikke nok brukbare GPS-punkter.» Strengene ligger direkte i koden, også i modell-laget — developmentRegion = nb, men ingen strengkatalog.

08

Brukerscenarioer

Slik flytene faktisk henger sammen i koden.

A · Den vanlige turen

  1. Trykk «Gå med Tintin» på klokkawatch

    HealthKit-autorisasjon, økt startes med .walking utendørs, GPS og retning slås på. «Gjør klar …» til låsen er god.

  2. Turen dukker opp på telefonen av seg selviphone

    Speilingen kobler seg på, live-kortet vises på hjemmeskjermen, og Live Activity tar plass på låseskjermen.

  3. Snusepausewatch

    «Pause» avslutter HealthKit-økten og starter en .pause-etappe. Kan også utløses fra telefonen eller fra sidetast-kommandoen.

  4. «Marker» ved vannetbegge

    Et WalkMarker med siste GPS-punkt, hapisk kvittering, og toast i 1,6 s.

  5. «Fullfør»watch

    Økt avsluttes, skritt hentes med en HKStatisticsQuery, ruten fortettes og skrives, TintinWalk bygges av alle etappene og sendes til telefonen.

  6. «Gåturen er lagret · ≈ 9 800 Tintin-steg 🐾»watch

    Hvis ruten ikke nådde Helse, sier klokka det eksplisitt her.

  7. Turen ligger i «Turene våre»iphone

    Kart, etappe-tidslinje, åtte måltall, notatfelt — og en ny linje i utforskningskartet.

B · Planlagt rute fra GPX

  1. Importer GPX i «Ruter»iphone

    Fra Filer eller en annen turapp. Navn foreslås fra filen, høydeprofil og distanse vises før lagring.

  2. Ruten synkroniseres automatiskiphone → watch

    Punktbudsjettet krymper den til klokkestørrelse. «N ruter synkroniseres automatisk».

  3. «Kjør til start»iphone

    Apple Kart åpnes med kjøreanvisning og trafikk til rutens startpunkt.

  4. «Velg rute» før eller under turenwatch

    Offline. Under tur sorteres rutene etter avstand fra deg, med «X unna» som undertekst.

  5. Ruten tegnes under sporet dittwatch

    Svart/hvit stiplet planlagt linje under din egen oransje strek, så avvik er synlig med ett blikk.

C · Bilturen midt i turen

  1. Du setter deg i bilen — turen går fortsattwatch

    Farten passerer 8,5 m/s. Punktene går i karantene, men ingenting skjer utad ennå.

  2. 12 sekunder senere: «Kjøring oppdaget»begge

    Haptisk varsel, gåetappen kuttes ved siste plausible gåpunkt, turen tvangspauses. «Registrert fart: opptil 47 km/t».

  3. Du velgerbegge

    Biltur gjør karantenen til en .driving-etappe og fortsetter opptaket i bilmodus. Fortsett spiller punktene tilbake. Avslutt lagrer det som finnes.

  4. Resultatet i Helseiphone

    Gåturen står som gåtur uten bilfart. Bilturen er registrert i «På tur», men gir aldri Tintin-steg og er alltid utelatt fra kjøredistansen i statistikken.

D · Navigasjon hjem

  1. «Mål» / «Finn destinasjon»begge

    Søk med MKLocalSearchCompleter, begrenset til 0,22° rundt deg, maks 12 forslag.

  2. Gårute beregnesbegge

    MKDirections med .walking, polylinjen komprimeres til 220 punkter og sendes over speilingen — begge veier, så telefonen kan sette et mål på klokka.

  3. Cyan linje på klokkekartetwatch

    Med «Mål»-markør. Telefonen viser gjenstående distanse og tid, regnet fra nærmeste navigasjonspunkt til der du faktisk er.

  4. «Avslutt navigasjon»begge

    En LiveNavigationUpdate med route: nil. Turen fortsetter uendret.

E · Når noe går galt

  1. Klokkeappen krasjer midt i turenwatch

    recoverActiveWorkoutSession kobler på igjen ved neste oppstart; tur-ID, etappe-ID og ferdige etapper leses fra UserDefaults. Turen fortsetter.

  2. Turen kom aldri over til telefoneniphone

    Ved hver aktivering spør telefonen Helse etter alt merket med appens metadata (og alt fra egen bundle siste 90 dager) og rekonstruerer turen med etapper og rute.

  3. Samme tur kommer to veieriphone

    Duplikatvinduet ±15 s / ±90 s fletter dem: lengste rute vinner, tomme felt fylles, ingenting overskrives destruktivt.

  4. Mosjon viser ikke kartetiphone

    «Kartdiagnostikk» under hundeprofilen leser siste økt, peker på hva som er galt, og kan lage en 61-punkts testtur for å isolere om feilen er appens eller Apples.

09

Krav vs. levert

Kravene ligger som et designark i Tidslinje_og_Logg/KRAV.png, under tittelen «Tur med Tintin». Status målt mot koden:

Krav (Apple Watch)StatusHvor
Velg rute — importert GPX eller tidligere turLevertRutevelger + «Lagre som fast rute»
Starte turen nåLevertGå / løp / kjør fra klar-skjermen
Spore tid, puls, kart, skritt, avstandLevertLive builder + HKStatisticsQuery for skritt
Vise vær og temperaturManglerIngen WeatherKit i kodebasen
Navigere med eller uten planlagt ruteLevertDestinasjonssøk + MKDirections
Pause og fortsettLevertEgne .pause-etapper
Lagre et sted underveisLevertWalkMarker, 4 typer
Synkronisere til Helse og MosjonDelvisSkriving er på plass; Mosjon-visning er nettopp derfor diagnoseverktøyet finnes
Krav (iPhone)StatusHvor
Se turdata og kart i appenLevertTurdetalj med 8 måltall og etappe-tidslinje
Tydelig hundeaktig UILevertPotemerke, potespor på kart, «Tintin-steg», hundeprofil
Administrere turer: importere, planlegge, historikkLevertRutebibliotek + historikk + rutegjenbruk
God statistikk: skritt, puls, kart over hvor vi går oftestDelvisUtforskningskart med ukedagsfarger og filtre; puls og skritt i turdetaljen
… inkludert vær og musikk/medierManglerIngen WeatherKit, ingen MediaPlayer/MusicKit

Verdt å merke: i den opprinnelige MVP-en var «vær, GPX-import og navigasjon» eksplisitt utenfor omfanget. To av de tre er nå levert.

10

Løse tråder

Observasjoner fra gjennomgangen, ikke feilrapporter — flere er bevisste valg.

Konfigurasjon

  • WATCHOS_DEPLOYMENT_TARGET = 10.0 mens iOS-målet er 26.0. Klokkekoden bruker API-er som er nyere enn watchOS 10, så tallet er sannsynligvis bare aldri oppdatert.
  • iOS-målet erklærer ingen UIBackgroundModes. Det stemmer med at telefonopptak er merket «Kun for simulator og testing», men PhoneWalkRecorder ber likevel om requestAlwaysAuthorization.
  • Ingen app-gruppe, tross innebygd widget-utvidelse. Live Activity oppdateres i stedet direkte fra appprosessen via ActivityKit, så det fungerer — men utvidelsen kan ikke lese delt data.
  • De to ExportOptions-filene med manuell signering mangler profil for com.ding.patur.liveactivity.
  • SWIFT_VERSION = 5.0, altså ingen Swift 6-språkmodus, selv om koden er gjennomført Sendable- og @MainActor-annotert.

Kode

  • Tre ulike desimeringsformler for samme problem: ceil(n/maks) i watchOptimized (240), ceil((n−1)/(maks−1)) i navigasjonspolylinjen (220), og ceil((n−1)/4999) i rutegjenbruk. Alle tre legger endepunktet tilbake til slutt.
  • Nesten all disk- og HealthKit-feilhåndtering er stille (try?, tomme callbacks). Unntaket er den syntetiske testturen, som rapporterer hvert steg presist — nyttig mønster som kunne vært brukt bredere.
  • LiveWorkoutMirrorManager fanger hundenavnet ved oppstart, så navneendring i profilen slår ikke inn i Live Activity før appen restartes.
  • Ingen tester i prosjektet. DrivingDetectionEngine er skrevet som en ren, plattformuavhengig verdi-type nettopp for å være enhetstestbar — den er det åpenbare stedet å begynne.
  • Design/AppIcon.svg er foreldet i forhold til build_app_icons.swift.

Naturlige neste steg

  • Vær er det eneste helt urørte kravet: WeatherKit ved turstart, lagret på turen, vist i turdetaljen og som filter i statistikken.
  • Medier — hva du hørte på turen — krever MPMusicPlayerController eller ShazamKit-lignende avlesning, og passer som et felt på etappen.
  • Frekvenskart: utforskningskartet viser hvor du har gått, men ikke hvor ofte. En rutenettbasert heatmap over de samme dataene ville svart direkte på kravet «hvor er der vi går oftest».

Gjennomgang av PaTur-main · versjon 0.3 (build 17) · 34 Swift-filer, 10 360 linjer · lest 1. september 2026.