Files
coach-ios/docs/SESSION-XCODE.md
Sylvain Bettinelli 089b6fbde6 Ce que la session Xcode du 11 aout a rencontre, et que le guide ne disait pas
L'etape 5 est faite. Cinq ecarts entre le guide et Xcode reel, tous corriges
dans le doc pour que la prochaine session ne les repaye pas :

Le template ne s'appelle plus CoachWatchWidgets.swift mais
CoachWatchWidgetsBundle.swift. Comme il porte un @main et que
CoachWatchComplications.swift en a deja un, l'oublier fait echouer la
compilation sur deux @main dans la meme target.

Les nouvelles targets sont creees en dossier synchronise : tout fichier du
dossier en est membre automatiquement, sans etre liste dans project.pbxproj.
CoachWatchComplications.swift etait donc deja inclus - d'ou son absence du
dialogue Add Files, qui ressemble a une panne et n'en est pas une. L'etape 6
du guide est desormais sans objet, et un grep qui renvoie 0 sur ce fichier
n'est plus un signal d'alarme.

Xcode cree la target en build 1 quand le projet est en 22, ce qui bloque a
l'installation. Les cases du template ont change (Include Control et Include
Configuration App Intent au lieu de Include Live Activity) : les deux se
decochent, nos complications sont des StaticConfiguration.

Et la Watch n'a pas besoin d'etre visible comme destination dans Xcode : la
phase Embed Watch Content embarque l'app Watch dans l'app iPhone, builder le
schema App suffit. Chercher la montre dans la liste des devices est une impasse
qui coute un mode developpeur et un redemarrage pour rien.

Ajout d'un script de verification de l'assemblage : il dit a quelle target
appartient chaque phase Embed. Embed Foundation Extensions doit pointer sur
CoachWatch - rattachee a App, l'extension resterait sur l'iPhone et aucune
complication n'apparaitrait.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 14:34:28 +00:00

15 KiB

Session Xcode — guide pas à pas

Écrit le 2026-08-03 pour Sylvain, à suivre sur le Mac mini.

Cette session livre tout le natif accumulé depuis le TestFlight de mai : login Apple/Google natif, watchOS live workout, widgets iPhone, complications Watch, et la routine quotidienne sur la montre.

Compte ~1 h 30 la première fois. Les étapes 1 à 6 mènent à une app qui tourne sur ton iPhone ; les étapes 7 à 9 la publient sur TestFlight. Tu peux t'arrêter après l'étape 6 et publier un autre jour.


Avant de commencer — ce qui a changé

La plupart des Swift sont déjà déclarés dans les bonnes targets (commits caeac37, f30cc60). Quatre ne le sont pas et doivent être ajoutés à la main — état vérifié dans project.pbxproj le 2026-08-11 :

Fichier Target Membership à cocher Étape
App/CoachQuickLog.swift App + CoachLiveActivity 4b
App/CoachQuickSync.swift App seule 4b
CoachLiveActivity/CoachQuickWidget.swift CoachLiveActivity seule 4b
CoachWatchWidgets/CoachWatchComplications.swift CoachWatchWidgets (target à créer) 5

⚠️ Les trois premiers sont arrivés le 2026-08-06, après la rédaction de ce guide, qui annonçait « une seule exception » — c'était vrai le 3 août. Sans eux, le widget « Saisie des repas et boissons » ne se compile pas. Leur runbook détaillé est docs/widgets-runbook-mac.md.

Déjà fait côté code, à ne pas refaire : CoachQuickWidget() est enregistré dans le WidgetBundle (CoachLiveActivityBundle.swift) et CoachQuickSync.flush() est appelé depuis AppDelegate (l. 57).

Ce que tu vas activer Étape
Login Apple + Google natif rien à faire, déjà câblé
Live workout watchOS rien à faire, déjà câblé
Routine sur la Watch rien à faire, déjà câblé
Widgets iPhone (Séance du jour, Score de forme) étapes 3-4 (App Group)
Widget Saisie rapide (eau, café, photo, scan) étape 4b (3 fichiers à ajouter)
Complications sur le cadran Watch étape 5 (facultative)

Étape 1 — Récupérer le code

⚠️ Xcode doit être FERMÉ (Cmd+Q, pas juste la fenêtre). Xcode réécrit le project.pbxproj depuis sa mémoire ; un git pull fenêtre ouverte nous a déjà bloqués deux fois.

cd ~/coach-ios
git pull
npm install
npx cap sync ios

Vérification — tu dois voir f30cc60 ou plus récent :

git log --oneline -1

Si npx cap sync râle sur la version de Node :

nvm use 22        # Capacitor 8 exige Node >= 22

Étape 2 — Le token d'auth (à ne pas oublier)

Le fichier CoachAuth.swift n'est pas versionné (il contient un secret). Sans lui, le projet ne compile pas.

ls ios/App/App/CoachAuth.swift
  • Le fichier existe → passe à l'étape 3.
  • Il n'existe pas → récupère le token puis crée-le :
# le token est sur le VPS
ssh ubuntu@83.228.246.229 'grep COACH_WEB_TOKEN ~/.config/infomaniak.env'

# puis, en remplaçant <TOKEN> par la valeur affichée :
cat > ios/App/App/CoachAuth.swift <<'EOF'
import Foundation
enum CoachAuth { static let kCoachWebToken: String = "<TOKEN>" }
EOF

Étape 3 — App Group sur le portail Apple

Nécessaire pour que les widgets lisent les données de l'app. À faire une seule fois, dans le navigateur.

  1. developer.apple.comCertificates, IDs & Profiles
  2. Identifiers → menu déroulant en haut à droite → App Groups
  3. Le groupe group.ch.hypnotruck.coach existe déjà ? → rien à faire. Sinon : +App Group → Description Coach Hypnotruck, Identifier group.ch.hypnotruck.coachContinueRegister

Étape 4 — Activer la capability dans Xcode

# Capacitor 8 utilise Swift Package Manager : il n'existe PAS de
# .xcworkspace (celui-ci supposait CocoaPods). On ouvre le .xcodeproj.
open ios/App/App.xcodeproj

Pour chacune de ces trois targets (colonne de gauche → projet App → onglet TARGETS) :

Target Capability à ajouter
App App Groups
CoachLiveActivity App Groups
CoachWatch App Groups

Pour chaque target : onglet Signing & Capabilities+ Capability (en haut à gauche) → tape « App Groups » → double-clic → coche group.ch.hypnotruck.coach dans la liste qui apparaît.

Si le groupe n'apparaît pas dans la liste, clique sur le bouton refresh sous la liste, ou re-connecte ton compte (Xcode → Settings → Accounts).

⚠️ Dès que c'est fait, commit + push — sinon ton prochain git pull sera bloqué par les modifications locales :

git add -A ios/App && git commit -m "chore(xcode): App Groups sur App, LiveActivity et Watch" && git push

Étape 4b — Les trois fichiers du widget « Saisie rapide » (~5 min)

Ils existent dans le repo depuis le 6 août mais n'appartiennent à aucune target : tant qu'ils n'y sont pas, le widget eau/café n'existe pas et l'extension ne compile pas.

Dans Xcode, clic droit sur le dossier de la cible → Add Files to "App"…décoche Copy items if needed → coche la ou les targets du tableau. Pour un fichier déjà présent dans l'arborescence, passe par l'inspecteur de droite (Cmd+Option+1) → Target Membership.

Fichier Cocher
ios/App/App/CoachQuickLog.swift App et CoachLiveActivity
ios/App/App/CoachQuickSync.swift App seule
ios/App/CoachLiveActivity/CoachQuickWidget.swift CoachLiveActivity seule

CoachQuickLog.swift doit bien être dans les deux cibles : l'extension écrit la file d'attente, l'app la vide.

Rien d'autre à câbler — CoachQuickWidget() est déjà dans le WidgetBundle et CoachQuickSync.flush() est déjà appelé par AppDelegate. Puis commit + push.

Étape 5 — Complications Watch (facultative, ~15 min)

Tu peux sauter cette étape et y revenir plus tard : tout le reste fonctionne sans. Elle n'ajoute que les complications sur le cadran de la montre.

Réalisée le 2026-08-11 — les écarts constatés sont intégrés ci-dessous.

  1. Menu File → New → Target…
  2. Onglet watchOSWidget ExtensionNext
  3. Product Name : CoachWatchWidgets. Les cases proposées ne sont plus celles du guide d'origine : Xcode propose désormais Include Control et Include Configuration App Intentdécoche les deux (nos complications sont des StaticConfiguration, sans paramètre à régler). Embed in Application : CoachWatchFinish
  4. Xcode propose d'activer le nouveau schéma → Activate
  5. Supprime le fichier template. Il ne s'appelle pas CoachWatchWidgets.swift mais CoachWatchWidgetsBundle.swift, et il porte un @main — or CoachWatchComplications.swift en a déjà un, et deux @main dans une target ne compilent pas. Le dossier étant synchronisé (point 6), un rm ios/App/CoachWatchWidgets/CoachWatchWidgetsBundle.swift suffit, Xcode le répercute. Bonne surprise : notre fichier déclare struct CoachWatchWidgetsBundle, le nom exact du template — rien à renommer.
  6. Étape sans objet sur Xcode récent. Les nouvelles targets sont créées en dossier synchronisé (PBXFileSystemSynchronizedRootGroup) : tout fichier présent dans le dossier de la target en est membre automatiquement, sans être listé dans project.pbxproj. CoachWatchComplications.swift y était déjà, il est donc déjà inclus — d'où son absence du dialogue Add Files, et un grep CoachWatchComplications project.pbxproj qui renvoie 0 sans que ce soit un problème. Vérifier plutôt : grep -c PBXFileSystemSynchronizedRootGroup project.pbxproj ≥ 1.
  7. CoachWidgetSnapshot.swift vit dans ios/App/App/, hors du dossier synchronisé : lui doit être coché à la main. Cmd+Shift+O → tape son nom → Cmd+Option+1Target Membership → coche CoachWatchWidgets sans décocher le reste. Sans lui : cannot find 'CoachWidgetStore' in scope.
  8. Target CoachWatchWidgetsSigning & Capabilities+ CapabilityApp Groups → coche group.ch.hypnotruck.coach
  9. ⚠️ Aligner le numéro de build. Xcode crée la target en build 1 alors que le projet est en 22The CFBundleVersion of an app extension ('1') must match that of its containing parent app ('22'). Target → GeneralIdentityVersion 1.2, Build 22. Contrôle : grep -n CURRENT_PROJECT_VERSION project.pbxproj | grep -v "= 22;" ne doit rien renvoyer.

Puis commit + push (même raison qu'à l'étape 4).

Vérifier l'assemblage sans cliquer partout

Cmd+S d'abord (Xcode n'écrit le projet sur disque qu'à l'enregistrement) :

# à quelle target appartient la phase qui embarque l'extension ?
python3 - <<'PY'
import re, pathlib
s = pathlib.Path('ios/App/App.xcodeproj/project.pbxproj').read_text()
emb = dict(re.findall(r'(\w+) /\* (Embed \w[\w ]*) \*/ = \{', s))
for m in re.finditer(r'/\* ([\w.-]+) \*/ = \{\s*isa = PBXNativeTarget;(.*?)\n\t\t\};', s, re.S):
    for pid, pname in emb.items():
        if pid in m.group(2):
            print(f"{pname:<32} -> target {m.group(1)}")
PY

Embed Foundation Extensions -> target CoachWatch : l'extension part bien sur la montre. Si elle est rattachée à App, elle reste sur l'iPhone et aucune complication n'apparaîtra jamais.

⚠️ La Watch n'a pas besoin d'apparaître comme destination dans Xcode. La phase Embed Watch Content embarque CoachWatch.app dans l'app iPhone : builder le schéma App sur l'iPhone installe l'app Watch et son extension. Pour voir la montre comme destination il faudrait activer le Mode développeur dessus (Réglages → Confidentialité et sécurité), ce qui n'est pas nécessaire ici.

Étape 6 — Compiler et tester sur l'iPhone

  1. Branche l'iPhone, déverrouille-le, avec l'Apple Watch au poignet et appairée
  2. En haut de Xcode, sélectionne le schéma App et ton iPhone comme device
  3. Product → Clean Build Folder (Cmd+Shift+K)
  4. Run (Cmd+R)

Si ça ne compile pas

Erreur Cause Solution
cannot find 'CoachAuth' in scope étape 2 sautée crée CoachAuth.swift
no such module 'Capacitor' sync manquant npx cap sync ios, Xcode fermé
erreur de signing profil pas régénéré Signing & Capabilities → décoche/recoche Automatically manage signing
CoachWidgetStore introuvable Target Membership vérifie que CoachWidgetSnapshot.swift est coché pour la target qui râle

Tests, dans l'ordre (5 min)

  1. L'app se lance et tu es connecté → le cookie natif fonctionne.
  2. Live workout (le seul flux déjà validé, à ne pas casser) : démarre une séance sur la Watch → la FC doit apparaître sur /live de l'iPhone. ⚠️ C'est le test le plus important : j'ai touché à CoachLiveBridge pour faire passer la routine. Si la FC ne remonte plus, dis-le-moi.
  3. Routine sur la Watch : ouvre /routine sur l'iPhone (ça pousse les blocs vers la montre) → app Coach sur la Watch → Routine du jour → les 6 blocs s'affichent → coche-en un → la page iPhone se met à jour sans recharger.
  4. Widgets iPhone : écran d'accueil → appui long → + → cherche Coach → ajoute « Séance du jour » et « Score de forme ».
  5. Login natif : déconnecte-toi, teste Se connecter avec Apple et Google.

Vérification serveur de la routine :

ssh ubuntu@83.228.246.229 'cat /home/ubuntu/coach_sportif/data/routine_log.json'

Si tu t'arrêtes ici, c'est déjà un succès : ton iPhone a tout le natif.


Étape 7 — Préparer la distribution

Deux réglages avant d'archiver.

a. Numéro de build — target App → onglet General : Version = 1.3, Build = 23 (le repo est à 1.2 / 22).

b. Push notifications en productionios/App/App/App.entitlements contient aps-environment = development. En TestFlight, les notifications push distantes ne marcheront pas avec cette valeur.

Le plus simple : laisse Xcode gérer. Avec Automatically manage signing, le profil de distribution force production à l'archivage. Vérifie-le après l'étape 8 : Organizer → clic droit sur l'archive → Show in Finder → clic droit → Afficher le contenuProducts/Applications/App.app → clic droit → Afficher le contenu → ouvre embedded.mobileprovision avec TextEdit → cherche aps-environment, il doit valoir production.

Si ce n'est pas le cas, édite App.entitlements en mettant production, ré-archive, et remets development ensuite pour ne pas casser tes builds de dev.

Étape 8 — Archiver

  1. En haut, remplace ton iPhone par Any iOS Device (arm64)
  2. Product → Archive (compte 5-10 min)
  3. La fenêtre Organizer s'ouvre sur l'archive

Si Archive est grisé : tu es encore sur un simulateur, repasse en Any iOS Device.

Étape 9 — Envoyer sur TestFlight

  1. Dans l'Organizer : Distribute AppApp Store ConnectUpload
  2. Laisse les options par défaut → NextUpload
  3. Attends 5-30 min qu'Apple finisse le processing (mail de confirmation)
  4. appstoreconnect.apple.comCoach HypnotruckTestFlight → le build 23 apparaît
  5. ⚠️ Assigne le build au groupe de testeurs internes — sans ça, il n'apparaît pas dans TestFlight sur ton iPhone. Ça nous a bloqués en mai : Internal Testing → ton groupe → + → coche le build 23
  6. Sur l'iPhone : app TestFlightMettre à jour

⚠️ Le cache WKWebView est tenace. Si l'app affiche encore l'ancienne interface après mise à jour : désinstalle l'app puis réinstalle depuis TestFlight.


En cas de blocage

Note l'étape et le message d'erreur exact, et envoie-les-moi. Utile à joindre :

# état du repo
cd ~/coach-ios && git log --oneline -3 && git status -sb

# les fichiers Swift sont-ils tous câblés ? (doit ne rien afficher sauf
# CoachWatchComplications si tu as sauté l'étape 5)
# -not -path '*/build/*' est indispensable : DerivedData et les checkouts SPM
# contiennent des centaines de .swift qui n'ont rien à faire dans le pbxproj
for f in $(find ios -name '*.swift' -not -path '*/build/*' -not -path '*/CapApp-SPM/*'); do
  grep -q "$(basename $f)" ios/App/App.xcodeproj/project.pbxproj || echo "NON CÂBLÉ: $f"
done

Pour les erreurs de plugins à l'exécution : Safari → menu Développement → ton iPhone → coach.hypnotruck.ch → console :

Object.keys(Capacitor.Plugins).filter(k => k.startsWith('Coach'))
// doit lister : CoachWorkoutKit, CoachHealthRoute, CoachWorkoutObserver,
//               CoachLiveBridge, CoachAppleAuth, CoachGoogleAuth,
//               CoachWidgetBridge, CoachRoutineBridge

Rappels permanents

  • Toujours quitter Xcode avant un git pull.
  • Toujours commit + push après une modification faite dans Xcode (capability, signing, target) — sinon le prochain pull est bloqué.
  • Les changements web (templates, CSS, routes) ne demandent aucun build : ils arrivent au prochain lancement de l'app.