watchOS Workout Apps in Xamarin
Cet article décrit les améliorations apportées par Apple aux applications d’entraînement dans watchOS 3 et comment les implémenter dans Xamarin.
Nouveautés de watchOS 3, les applications associées à l’entraînement ont la possibilité de s’exécuter en arrière-plan sur Apple Watch et d’accéder aux données HealthKit. Leur application iOS 10 parente a également la possibilité de lancer l’application watchOS 3 sans intervention de l’utilisateur.
Les rubriques suivantes sont traitées en détail :
À propos des applications d’entraînement
Les utilisateurs d’applications de fitness et d’entraînement peuvent être hautement dédiés, en dévotant plusieurs heures de la journée à leurs objectifs de santé et de fitness. Par conséquent, ils s’attendent à des applications réactives et faciles à utiliser qui collectent et affichent avec précision les données et s’intègrent en toute transparence à Apple Health.
Une application de fitness ou d’entraînement bien conçue aide les utilisateurs à tracer leurs activités pour atteindre leurs objectifs de mise en forme. En utilisant les applications Apple Watch, de fitness et d’entraînement ont un accès instantané à la fréquence cardiaque, à la brûlure de calories et à la détection d’activité.
Nouveautés de watchOS 3, Background Running offre aux applications associées à l’entraînement la possibilité de s’exécuter en arrière-plan sur Apple Watch et d’accéder aux données HealthKit.
Ce document présente la fonctionnalité Background Running, couvre le cycle de vie de l’application d’entraînement et montre comment une application d’entraînement peut contribuer aux anneaux d’activité de l’utilisateur sur l’Apple Watch.
À propos des sessions d’entraînement
Le cœur de chaque application d’entraînement est une session d’entraînement (HKWorkoutSession
) que l’utilisateur peut démarrer et arrêter. L’API Session d’entraînement est facile à implémenter et offre plusieurs avantages à une application d’entraînement, par exemple :
- Détection des brûlures de mouvement et de calories en fonction du type d’activité.
- Contribution automatique aux anneaux d’activité de l’utilisateur.
- Dans une session, l’application s’affiche automatiquement chaque fois que l’utilisateur réveille l’appareil (soit en soulevant son poignet, soit en interagissant avec l’Apple Watch).
À propos de l’exécution en arrière-plan
Comme indiqué ci-dessus, avec watchOS 3, une application d’entraînement peut être définie pour s’exécuter en arrière-plan. L’utilisation de l’arrière-plan d’une application d’entraînement peut traiter les données des capteurs d’Apple Watch lors de l’exécution en arrière-plan. Par exemple, une application peut continuer à surveiller la fréquence cardiaque de l’utilisateur, même si elle n’est plus affichée à l’écran.
L’exécution en arrière-plan permet également de présenter des commentaires en direct à l’utilisateur à tout moment pendant une session d’entraînement active, par exemple l’envoi d’une alerte haptique pour informer l’utilisateur de sa progression actuelle.
En outre, l’exécution en arrière-plan permet à l’application de mettre à jour rapidement son interface utilisateur afin que l’utilisateur dispose des données les plus récentes lorsqu’il regarde rapidement son Apple Watch.
Pour maintenir des performances élevées sur Apple Watch, une application espion à l’aide de l’exécution en arrière-plan doit limiter la quantité de travail en arrière-plan pour économiser la batterie. Si une application utilise un processeur excessif en arrière-plan, elle peut être suspendue par watchOS.
Activation de l’exécution en arrière-plan
Pour activer l’exécution en arrière-plan, procédez comme suit :
Dans le Explorateur de solutions, double-cliquez sur le fichier de l’application i Téléphone complémentaire de l’extension
Info.plist
Watch pour l’ouvrir pour modification.Basculez vers la vue Source :
Ajoutez une nouvelle clé appelée
WKBackgroundModes
et définissez le type surArray
:Ajoutez un nouvel élément au tableau avec le type et
String
une valeur deworkout-processing
:Enregistrez les modifications du fichier.
Démarrage d’une session d’entraînement
Il existe trois étapes principales pour commencer une session d’entraînement :
- L’application doit demander l’autorisation d’accéder aux données dans HealthKit.
- Créez un objet De configuration d’entraînement pour le type d’entraînement démarré.
- Créez et démarrez une session d’entraînement à l’aide de la configuration d’entraînement nouvellement créée.
Demande d’autorisation
Pour qu’une application puisse accéder aux données HealthKit de l’utilisateur, elle doit demander et recevoir l’autorisation de l’utilisateur. En fonction de la nature de l’application d’entraînement, il peut effectuer les types de demandes suivants :
- Autorisation d’écrire des données :
- Entraînements
- Autorisation de lecture des données :
- Énergie brûlée
- Distance
- Fréquence cardiaque
Pour qu’une application puisse demander une autorisation, elle doit être configurée pour accéder à HealthKit.
Effectuez les actions suivantes :
Dans l’Explorateur de solutions, double-cliquez sur le fichier
Entitlements.plist
pour l’ouvrir et le modifier.Faites défiler jusqu’au bas et case activée Activer HealthKit :
Enregistrez les modifications du fichier.
Suivez les instructions de l’ID d’application explicite et du profil d’approvisionnement et associez l’ID d’application et le profil d’approvisionnement à vos sections d’application Xamarin.iOS de l’article Introduction to HealthKit pour provisionner correctement l’application.
Enfin, utilisez les instructions du Kit d’intégrité de programmation et demande l’autorisation de l’utilisateur dans l’article Introduction à HealthKit pour demander l’autorisation d’accéder au magasin de données HealthKit de l’utilisateur.
Définition de la configuration de l’entraînement
Les sessions d’entraînement sont créées à l’aide d’un objet De configuration d’entraînement (HKWorkoutConfiguration
) qui spécifie le type d’entraînement (par exemple HKWorkoutActivityType.Running
) et l’emplacement d’entraînement (par HKWorkoutSessionLocationType.Outdoor
exemple) :
using HealthKit;
...
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
Création d’un délégué de session d’entraînement
Pour gérer les événements qui peuvent se produire pendant une session d’entraînement, l’application doit créer une instance de délégué de session d’entraînement. Ajoutez une nouvelle classe au projet et basez-la hors de la HKWorkoutSessionDelegate
classe. Pour l’exemple d’une course extérieure, il peut ressembler à ce qui suit :
using System;
using Foundation;
using WatchKit;
using HealthKit;
namespace MonkeyWorkout.MWWatchExtension
{
public class OutdoorRunDelegate : HKWorkoutSessionDelegate
{
#region Computed Properties
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
#endregion
#region Constructors
public OutdoorRunDelegate (HKHealthStore healthStore, HKWorkoutSession workoutSession)
{
// Initialize
this.HealthStore = healthStore;
this.WorkoutSession = workoutSession;
// Attach this delegate to the session
workoutSession.Delegate = this;
}
#endregion
#region Override Methods
public override void DidFail (HKWorkoutSession workoutSession, NSError error)
{
// Handle workout session failing
RaiseFailed ();
}
public override void DidChangeToState (HKWorkoutSession workoutSession, HKWorkoutSessionState toState, HKWorkoutSessionState fromState, NSDate date)
{
// Take action based on the change in state
switch (toState) {
case HKWorkoutSessionState.NotStarted:
break;
case HKWorkoutSessionState.Paused:
RaisePaused ();
break;
case HKWorkoutSessionState.Running:
RaiseRunning ();
break;
case HKWorkoutSessionState.Ended:
RaiseEnded ();
break;
}
}
public override void DidGenerateEvent (HKWorkoutSession workoutSession, HKWorkoutEvent @event)
{
base.DidGenerateEvent (workoutSession, @event);
}
#endregion
#region Events
public delegate void OutdoorRunEventDelegate ();
public event OutdoorRunEventDelegate Failed;
internal void RaiseFailed ()
{
if (this.Failed != null) this.Failed ();
}
public event OutdoorRunEventDelegate Paused;
internal void RaisePaused ()
{
if (this.Paused != null) this.Paused ();
}
public event OutdoorRunEventDelegate Running;
internal void RaiseRunning ()
{
if (this.Running != null) this.Running ();
}
public event OutdoorRunEventDelegate Ended;
internal void RaiseEnded ()
{
if (this.Ended != null) this.Ended ();
}
#endregion
}
}
Cette classe crée plusieurs événements qui seront déclenchés en tant qu’état de la session d’entraînement (DidChangeToState
) et si la session d’entraînement échoue (DidFail
).
Création d’une session d’entraînement
À l’aide de la configuration d’entraînement et du délégué de session d’entraînement créés ci-dessus pour créer une session d’entraînement et la démarrer sur le magasin HealthKit par défaut de l’utilisateur :
using HealthKit;
...
#region Computed Properties
public HKHealthStore HealthStore { get; set;} = new HKHealthStore ();
public OutdoorRunDelegate RunDelegate { get; set; }
#endregion
...
private void StartOutdoorRun ()
{
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
// Create workout session
// Start workout session
NSError error = null;
var workoutSession = new HKWorkoutSession (configuration, out error);
// Successful?
if (error != null) {
// Report error to user and return
return;
}
// Create workout session delegate and wire-up events
RunDelegate = new OutdoorRunDelegate (HealthStore, workoutSession);
RunDelegate.Failed += () => {
// Handle the session failing
};
RunDelegate.Paused += () => {
// Handle the session being paused
};
RunDelegate.Running += () => {
// Handle the session running
};
RunDelegate.Ended += () => {
// Handle the session ending
};
// Start session
HealthStore.StartWorkoutSession (workoutSession);
}
Si l’application démarre cette session d’entraînement et que l’utilisateur revient à son visage de montre, une icône « homme en cours d’exécution » verte s’affiche au-dessus du visage :
Si l’utilisateur appuie sur cette icône, il est renvoyé à l’application.
Collecte et contrôle des données
Une fois qu’une session d’entraînement a été configurée et démarrée, l’application doit collecter des données sur la session (par exemple, la fréquence cardiaque de l’utilisateur) et contrôler l’état de la session :
- Observation d’exemples : l’application doit récupérer des informations de HealthKit qui seront exécutées et affichées à l’utilisateur.
- Observation d’événements : l’application doit répondre aux événements générés par HealthKit ou à partir de l’interface utilisateur de l’application (par exemple, l’utilisateur interrompt l’entraînement).
- Entrer l’état d’exécution : la session a été démarrée et est en cours d’exécution.
- Entrez l’état suspendu : l’utilisateur a suspendu la session d’entraînement actuelle et peut le redémarrer à une date ultérieure. L’utilisateur peut basculer entre les états en cours d’exécution et suspendus plusieurs fois dans une session d’entraînement unique.
- Fin de session d’entraînement - À tout moment, l’utilisateur peut mettre fin à la session d’entraînement ou il peut expirer et se terminer par lui-même s’il s’agissait d’une séance d’entraînement limitée (par exemple, une course de deux milles).
La dernière étape consiste à enregistrer les résultats de la session d’entraînement dans le magasin de données HealthKit de l’utilisateur.
Observation d’exemples HealthKit
L’application doit ouvrir une requête d’objet Anchor pour chacun des points de données HealthKit qui lui intéressent, comme la fréquence cardiaque ou l’énergie active brûlée. Pour chaque point de données observé, un gestionnaire de mises à jour doit être créé pour capturer de nouvelles données à mesure qu’elles sont envoyées à l’application.
À partir de ces points de données, l’application peut accumuler des totaux (par exemple, la distance d’exécution totale) et mettre à jour son interface utilisateur en fonction des besoins. En outre, l’application peut avertir les utilisateurs lorsqu’ils ont atteint un objectif ou une réalisation spécifique, par exemple terminer le mile suivant d’une exécution.
Examinez l’exemple de code suivant :
private void ObserveHealthKitSamples ()
{
// Get the starting date of the required samples
var datePredicate = HKQuery.GetPredicateForSamples (WorkoutSession.StartDate, null, HKQueryOptions.StrictStartDate);
// Get data from the local device
var devices = new NSSet<HKDevice> (new HKDevice [] { HKDevice.LocalDevice });
var devicePredicate = HKQuery.GetPredicateForObjectsFromDevices (devices);
// Assemble compound predicate
var queryPredicate = NSCompoundPredicate.CreateAndPredicate (new NSPredicate [] { datePredicate, devicePredicate });
// Get ActiveEnergyBurned
var queryActiveEnergyBurned = new HKAnchoredObjectQuery (HKQuantityType.Create (HKQuantityTypeIdentifier.ActiveEnergyBurned), queryPredicate, null, HKSampleQuery.NoLimit, (query, addedObjects, deletedObjects, newAnchor, error) => {
// Valid?
if (error == null) {
// Yes, process all returned samples
foreach (HKSample sample in addedObjects) {
var quantitySample = sample as HKQuantitySample;
ActiveEnergyBurned += quantitySample.Quantity.GetDoubleValue (HKUnit.Joule);
}
// Update User Interface
...
}
});
// Start Query
HealthStore.ExecuteQuery (queryActiveEnergyBurned);
}
Il crée un prédicat pour définir la date de début à laquelle il souhaite obtenir des données à l’aide de la GetPredicateForSamples
méthode. Il crée un ensemble d’appareils pour extraire les informations HealthKit à partir de la GetPredicateForObjectsFromDevices
méthode, dans ce cas, l’Apple Watch local uniquement (HKDevice.LocalDevice
). Les deux prédicats sont combinés en un prédicat composé (NSCompoundPredicate
) à l’aide de la CreateAndPredicate
méthode.
Un nouveau HKAnchoredObjectQuery
point de données est créé pour le point de données souhaité (dans ce cas HKQuantityTypeIdentifier.ActiveEnergyBurned
pour le point de données Actif Energy Burned), aucune limite n’est imposée à la quantité de données retournées (HKSampleQuery.NoLimit
) et un gestionnaire de mise à jour est défini pour gérer les données renvoyées à l’application à partir de HealthKit.
Le gestionnaire de mises à jour est appelé chaque fois que de nouvelles données sont remises à l’application pour le point de données donné. Si aucune erreur n’est retournée, l’application peut lire les données en toute sécurité, effectuer les calculs requis et mettre à jour son interface utilisateur selon les besoins.
Le code effectue une boucle sur tous les exemples (HKSample
) retournés dans le addedObjects
tableau et les convertit en un échantillon quantity (HKQuantitySample
). Ensuite, il obtient la double valeur de l’échantillon en tant que milliseconde (HKUnit.Joule
) et l’accumule dans le total en cours d’exécution de l’énergie active brûlée pour l’entraînement et met à jour l’interface utilisateur.
Notification d’objectif atteinte
Comme mentionné ci-dessus, lorsque l’utilisateur atteint un objectif dans l’application d’entraînement (par exemple, terminer le premier mile d’une exécution), il peut envoyer des commentaires haptiques à l’utilisateur via le moteur taptic. L’application doit également mettre à jour son interface utilisateur à ce stade, car l’utilisateur déclenchera plus que probablement son poignet pour voir l’événement qui a invité les commentaires.
Pour lire les commentaires haptiques, utilisez le code suivant :
// Play haptic feedback
WKInterfaceDevice.CurrentDevice.PlayHaptic (WKHapticType.Notification);
Observation d’événements
Les événements sont des horodatages que l’application peut utiliser pour mettre en surbrillance certains points pendant l’entraînement de l’utilisateur. Certains événements seront créés directement par l’application et enregistrés dans l’entraînement et certains événements seront créés automatiquement par HealthKit.
Pour observer les événements créés par HealthKit, l’application remplace la DidGenerateEvent
méthode du HKWorkoutSessionDelegate
:
using System.Collections.Generic;
...
public List<HKWorkoutEvent> WorkoutEvents { get; set; } = new List<HKWorkoutEvent> ();
...
public override void DidGenerateEvent (HKWorkoutSession workoutSession, HKWorkoutEvent @event)
{
base.DidGenerateEvent (workoutSession, @event);
// Save HealthKit generated event
WorkoutEvents.Add (@event);
// Take action based on the type of event
switch (@event.Type) {
case HKWorkoutEventType.Lap:
break;
case HKWorkoutEventType.Marker:
break;
case HKWorkoutEventType.MotionPaused:
break;
case HKWorkoutEventType.MotionResumed:
break;
case HKWorkoutEventType.Pause:
break;
case HKWorkoutEventType.Resume:
break;
}
}
Apple a ajouté les nouveaux types d’événements suivants dans watchOS 3 :
HKWorkoutEventType.Lap
- Sont destinés aux événements qui interrompent l’entraînement en portions de distance égales. Par exemple, pour marquer un tour autour d’une piste en cours d’exécution.HKWorkoutEventType.Marker
- Sont pour des points d’intérêt arbitraires au sein de l’entraînement. Par exemple, atteindre un point spécifique sur la route d’une course extérieure.
Ces nouveaux types peuvent être créés par l’application et stockés dans l’entraînement pour une utilisation ultérieure dans la création de graphiques et de statistiques.
Pour créer un événement de marqueur, procédez comme suit :
using System.Collections.Generic;
...
public float MilesRun { get; set; }
public List<HKWorkoutEvent> WorkoutEvents { get; set; } = new List<HKWorkoutEvent> ();
...
public void ReachedNextMile ()
{
// Create and save marker event
var markerEvent = HKWorkoutEvent.Create (HKWorkoutEventType.Marker, NSDate.Now);
WorkoutEvents.Add (markerEvent);
// Notify user
NotifyUserOfReachedMileGoal (++MilesRun);
}
Ce code crée une instance d’un événement marqueur (HKWorkoutEvent
) et l’enregistre dans une collection privée d’événements (qui seront écrits ultérieurement dans la session d’entraînement) et avertit l’utilisateur de l’événement via des haptics.
Suspension et reprise des séances d’entraînement
À tout moment dans une session d’entraînement, l’utilisateur peut suspendre temporairement l’entraînement et le reprendre ultérieurement. Par exemple, ils peuvent suspendre une exécution d’intérieur pour prendre un appel important et reprendre l’exécution une fois l’appel terminé.
L’interface utilisateur de l’application doit fournir un moyen de suspendre et de reprendre l’entraînement (en appelant HealthKit) afin que l’Apple Watch puisse conserver à la fois l’alimentation et l’espace de données pendant que l’utilisateur a suspendu son activité. En outre, l’application doit ignorer les nouveaux points de données qui peuvent être reçus lorsque la session d’entraînement est dans un état suspendu.
HealthKit répond aux appels de pause et de reprise en générant des événements Pause et Resume. Pendant que la session d’entraînement est suspendue, aucun nouvel événement ou données n’est envoyé à l’application par HealthKit jusqu’à ce que la session soit reprise.
Utilisez le code suivant pour suspendre et reprendre une session d’entraînement :
public HKHealthStore HealthStore { get; set;} = new HKHealthStore ();
public HKWorkoutSession WorkoutSession { get; set;}
...
public void PauseWorkout ()
{
// Pause the current workout
HealthStore.PauseWorkoutSession (WorkoutSession);
}
public void ResumeWorkout ()
{
// Pause the current workout
HealthStore.ResumeWorkoutSession (WorkoutSession);
}
Les événements Pause et Resume qui seront générés à partir de HealthKit peuvent être gérés en remplaçant la DidGenerateEvent
méthode du HKWorkoutSessionDelegate
:
public override void DidGenerateEvent (HKWorkoutSession workoutSession, HKWorkoutEvent @event)
{
base.DidGenerateEvent (workoutSession, @event);
// Take action based on the type of event
switch (@event.Type) {
case HKWorkoutEventType.Pause:
break;
case HKWorkoutEventType.Resume:
break;
}
}
Événements de mouvement
En outre, les événements Motion Paused (HKWorkoutEventType.MotionPaused
) et Motion Resumed (HKWorkoutEventType.MotionResumed
) sont également nouveaux pour watchOS 3. Ces événements sont déclenchés automatiquement par HealthKit lors d’une séance d’entraînement en cours d’exécution lorsque l’utilisateur démarre et cesse de se déplacer.
Lorsque l’application reçoit un événement Motion Paused, elle doit cesser de collecter des données jusqu’à ce que l’utilisateur reprend le mouvement et que l’événement Motion Resumes soit reçu. L’application ne doit pas suspendre la session d’entraînement en réponse à un événement motion suspendu.
Important
Les événements Motion Paused and Motion Resume ne sont pris en charge que pour le type d’activité RunningWorkout (HKWorkoutActivityType.Running
).
Là encore, ces événements peuvent être gérés en remplaçant la DidGenerateEvent
méthode du HKWorkoutSessionDelegate
:
public override void DidGenerateEvent (HKWorkoutSession workoutSession, HKWorkoutEvent @event)
{
base.DidGenerateEvent (workoutSession, @event);
// Take action based on the type of event
switch (@event.Type) {
case HKWorkoutEventType.MotionPaused:
break;
case HKWorkoutEventType.MotionResumed:
break;
}
}
Fin et enregistrement de la session d’entraînement
Lorsque l’utilisateur a terminé son entraînement, l’application doit mettre fin à la session d’entraînement actuelle et l’enregistrer dans la base de données HealthKit. Les séances d’entraînement enregistrées dans HealthKit sont automatiquement affichées dans la liste des activités d’entraînement.
Nouveautés d’iOS 10, cela inclut également la liste des activités d’entraînement sur l’i Téléphone de l’utilisateur. Ainsi, même si l’Apple Watch n’est pas à proximité, l’entraînement sera présenté sur le téléphone.
Les séances d’entraînement qui incluent Energy Samples mettent à jour l’anneau de déplacement de l’utilisateur dans l’application Activités afin que les applications tierces puissent désormais contribuer aux objectifs de déplacement quotidiens de l’utilisateur.
Les étapes suivantes sont nécessaires pour terminer et enregistrer une session d’entraînement :
- Tout d’abord, l’application doit mettre fin à la session d’entraînement.
- La session d’entraînement est enregistrée dans HealthKit.
- Ajoutez des échantillons (comme l’énergie brûlée ou la distance) à la session d’entraînement enregistrée.
Fin de la session
Pour mettre fin à la session d’entraînement, appelez la EndWorkoutSession
méthode du passage dans le HKHealthStore
HKWorkoutSession
:
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
...
public void EndOutdoorRun ()
{
// End the current workout session
HealthStore.EndWorkoutSession (WorkoutSession);
}
Cela réinitialise les capteurs des appareils en mode normal. Lorsque HealthKit termine l’entraînement, il reçoit un rappel à la DidChangeToState
méthode du HKWorkoutSessionDelegate
:
public override void DidChangeToState (HKWorkoutSession workoutSession, HKWorkoutSessionState toState, HKWorkoutSessionState fromState, NSDate date)
{
// Take action based on the change in state
switch (toState) {
...
case HKWorkoutSessionState.Ended:
StopObservingHealthKitSamples ();
RaiseEnded ();
break;
}
}
Enregistrement de la session
Une fois que l’application a terminé la session d’entraînement, elle devra créer une séance d’entraînement (HKWorkout
) et l’enregistrer (ainsi qu’un événement) dans le magasin de données HealthKit (HKHealthStore
) :
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
public float MilesRun { get; set; }
public double ActiveEnergyBurned { get; set;}
public List<HKWorkoutEvent> WorkoutEvents { get; set; } = new List<HKWorkoutEvent> ();
...
private void SaveWorkoutSession ()
{
// Build required workout quantities
var energyBurned = HKQuantity.FromQuantity (HKUnit.Joule, ActiveEnergyBurned);
var distance = HKQuantity.FromQuantity (HKUnit.Mile, MilesRun);
// Create any required metadata
var metadata = new NSMutableDictionary ();
metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));
// Create workout
var workout = HKWorkout.Create (HKWorkoutActivityType.Running,
WorkoutSession.StartDate,
NSDate.Now,
WorkoutEvents.ToArray (),
energyBurned,
distance,
metadata);
// Save to HealthKit
HealthStore.SaveObject (workout, (successful, error) => {
// Handle any errors
if (error == null) {
// Was the save successful
if (successful) {
}
} else {
// Report error
}
});
}
Ce code crée la quantité totale d’énergie brûlée et de distance pour l’entraînement en tant qu’objets HKQuantity
. Un dictionnaire de métadonnées définissant l’entraînement est créé et l’emplacement de l’entraînement est spécifié :
metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));
Un nouvel HKWorkout
objet est créé avec le même HKWorkoutActivityType
que les HKWorkoutSession
dates de début et de fin, la liste des événements (cumulés à partir des sections ci-dessus), l’énergie brûlée, la distance totale et le dictionnaire de métadonnées. Cet objet est enregistré dans le Magasin d’intégrité et toutes les erreurs gérées.
Ajout d’exemples
Lorsque l’application enregistre un ensemble d’échantillons dans une séance d’entraînement, HealthKit génère une connexion entre les échantillons et l’entraînement lui-même afin que l’application puisse interroger HealthKit à une date ultérieure pour tous les échantillons associés à une séance d’entraînement donnée. À l’aide de ces informations, l’application peut générer des graphiques à partir des données d’entraînement et les tracer par rapport à un chronologie d’entraînement.
Pour qu’une application contribue à l’anneau de déplacement de l’application Activité, elle doit inclure des échantillons d’énergie avec l’entraînement enregistré. En outre, les totaux pour la distance et l’énergie doivent correspondre à la somme des échantillons que l’application associe à un entraînement enregistré.
Pour ajouter des exemples à une séance d’entraînement enregistrée, procédez comme suit :
using System.Collections.Generic;
using WatchKit;
using HealthKit;
...
public HKHealthStore HealthStore { get; private set; }
public List<HKSample> WorkoutSamples { get; set; } = new List<HKSample> ();
...
private void SaveWorkoutSamples (HKWorkout workout)
{
// Add samples to saved workout
HealthStore.AddSamples (WorkoutSamples.ToArray (), workout, (success, error) => {
// Handle any errors
if (error == null) {
// Was the save successful
if (success) {
}
} else {
// Report error
}
});
}
Si vous le souhaitez, l’application peut calculer et créer un sous-ensemble plus petit d’échantillons ou un méga échantillon (couvrant toute la plage de l’entraînement) qui est ensuite associé à l’entraînement enregistré.
Entraînements et iOS 10
Chaque application d’entraînement watchOS 3 a une application d’entraînement parente iOS 10 et, nouveauté d’iOS 10, cette application iOS peut être utilisée pour démarrer un entraînement qui place l’Apple Watch en mode entraînement (sans intervention de l’utilisateur) et exécute l’application watchOS en mode Exécution en arrière-plan (voir À propos de l’exécution en arrière-plan ci-dessus pour plus de détails).
Pendant que l’application watchOS est en cours d’exécution, elle peut utiliser Watch Connecter ivity pour la messagerie et la communication avec l’application iOS parente.
Examinez le fonctionnement de ce processus :
- L’application i Téléphone crée un
HKWorkoutConfiguration
objet et définit le type d’entraînement et l’emplacement. - L’objet
HKWorkoutConfiguration
est envoyé la version Apple Watch de l’application et, s’il n’est pas déjà en cours d’exécution, il est démarré par le système. - À l’aide de la configuration de l’entraînement passée, l’application watchOS 3 démarre une nouvelle session d’entraînement (
HKWorkoutSession
).
Important
Pour que l’application i Téléphone parente démarre une séance d’entraînement sur Apple Watch, l’application watchOS 3 doit avoir l’option Background Running activée. Pour plus d’informations, consultez l’activation de l’arrière-plan en cours d’exécution ci-dessus.
Ce processus est très similaire au processus de démarrage d’une session d’entraînement dans l’application watchOS 3 directement. Sur i Téléphone, utilisez le code suivant :
using System;
using HealthKit;
using WatchConnectivity;
...
#region Computed Properties
public HKHealthStore HealthStore { get; set; } = new HKHealthStore ();
public WCSession ConnectivitySession { get; set; } = WCSession.DefaultSession;
#endregion
...
private void StartOutdoorRun ()
{
// Can the app communicate with the watchOS version of the app?
if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
// Start watch app
HealthStore.StartWatchApp (configuration, (success, error) => {
// Handle any errors
if (error == null) {
// Was the save successful
if (success) {
...
}
} else {
// Report error
...
}
});
}
}
Ce code garantit que la version watchOS de l’application est installée et que la version i Téléphone peut d’abord se connecter à celle-ci :
if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
...
}
Ensuite, il crée une HKWorkoutConfiguration
méthode comme d’habitude et utilise la StartWatchApp
méthode de l’envoyer HKHealthStore
à l’Apple Watch et démarrer l’application et la session d’entraînement.
Sur l’application espion du système d’exploitation, utilisez le code suivant dans :WKExtensionDelegate
using WatchKit;
using HealthKit;
...
#region Computed Properties
public HKHealthStore HealthStore { get; set;} = new HKHealthStore ();
public OutdoorRunDelegate RunDelegate { get; set; }
#endregion
...
public override void HandleWorkoutConfiguration (HKWorkoutConfiguration workoutConfiguration)
{
// Create workout session
// Start workout session
NSError error = null;
var workoutSession = new HKWorkoutSession (workoutConfiguration, out error);
// Successful?
if (error != null) {
// Report error to user and return
return;
}
// Create workout session delegate and wire-up events
RunDelegate = new OutdoorRunDelegate (HealthStore, workoutSession);
RunDelegate.Failed += () => {
// Handle the session failing
};
RunDelegate.Paused += () => {
// Handle the session being paused
};
RunDelegate.Running += () => {
// Handle the session running
};
RunDelegate.Ended += () => {
// Handle the session ending
};
// Start session
HealthStore.StartWorkoutSession (workoutSession);
}
Elle prend et HKWorkoutConfiguration
crée une HKWorkoutSession
instance personnalisée et attache une instance de la commande personnalisée HKWorkoutSessionDelegate
. La session d’entraînement est démarrée sur healthKit Health Store de l’utilisateur.
Rassembler toutes les pièces ensemble
Prendre toutes les informations présentées dans ce document, une application d’entraînement watchOS 3 et son application d’entraînement parente iOS 10 peut inclure les parties suivantes :
- iOS 10
ViewController.cs
- Gère le démarrage d’une session watch Connecter ivity et une séance d’entraînement sur l’Apple Watch. - watchOS 3
ExtensionDelegate.cs
- Gère la version watchOS 3 de l’application d’entraînement. - watchOS 3
OutdoorRunDelegate.cs
- PersonnaliséHKWorkoutSessionDelegate
pour gérer les événements pour l’entraînement.
Important
Le code présenté dans les sections suivantes inclut uniquement les parties requises pour implémenter les nouvelles fonctionnalités améliorées fournies aux applications d’entraînement dans watchOS 3. Tout le code de prise en charge et le code à présenter et mettre à jour l’interface utilisateur ne sont pas inclus, mais peuvent être facilement créés en suivant notre autre documentation watchOS.
ViewController.cs
Le ViewController.cs
fichier dans la version iOS 10 parente de l’application d’entraînement inclut le code suivant :
using System;
using HealthKit;
using UIKit;
using WatchConnectivity;
namespace MonkeyWorkout
{
public partial class ViewController : UIViewController
{
#region Computed Properties
public HKHealthStore HealthStore { get; set; } = new HKHealthStore ();
public WCSession ConnectivitySession { get; set; } = WCSession.DefaultSession;
#endregion
#region Constructors
protected ViewController (IntPtr handle) : base (handle)
{
// Note: this .ctor should not contain any initialization logic.
}
#endregion
#region Private Methods
private void InitializeWatchConnectivity ()
{
// Is Watch Connectivity supported?
if (!WCSession.IsSupported) {
// No, abort
return;
}
// Is the session already active?
if (ConnectivitySession.ActivationState != WCSessionActivationState.Activated) {
// No, start session
ConnectivitySession.ActivateSession ();
}
}
private void StartOutdoorRun ()
{
// Can the app communicate with the watchOS version of the app?
if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
// Start watch app
HealthStore.StartWatchApp (configuration, (success, error) => {
// Handle any errors
if (error == null) {
// Was the save successful
if (success) {
...
}
} else {
// Report error
...
}
});
}
}
#endregion
#region Override Methods
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Start Watch Connectivity
InitializeWatchConnectivity ();
}
#endregion
}
}
ExtensionDelegate.cs
Le ExtensionDelegate.cs
fichier de la version watchOS 3 de l’application d’entraînement inclut le code suivant :
using System;
using Foundation;
using WatchKit;
using HealthKit;
namespace MonkeyWorkout.MWWatchExtension
{
public class ExtensionDelegate : WKExtensionDelegate
{
#region Computed Properties
public HKHealthStore HealthStore { get; set;} = new HKHealthStore ();
public OutdoorRunDelegate RunDelegate { get; set; }
#endregion
#region Constructors
public ExtensionDelegate ()
{
}
#endregion
#region Private Methods
private void StartWorkoutSession (HKWorkoutConfiguration workoutConfiguration)
{
// Create workout session
// Start workout session
NSError error = null;
var workoutSession = new HKWorkoutSession (workoutConfiguration, out error);
// Successful?
if (error != null) {
// Report error to user and return
return;
}
// Create workout session delegate and wire-up events
RunDelegate = new OutdoorRunDelegate (HealthStore, workoutSession);
RunDelegate.Failed += () => {
// Handle the session failing
...
};
RunDelegate.Paused += () => {
// Handle the session being paused
...
};
RunDelegate.Running += () => {
// Handle the session running
...
};
RunDelegate.Ended += () => {
// Handle the session ending
...
};
RunDelegate.ReachedMileGoal += (miles) => {
// Handle the reaching a session goal
...
};
RunDelegate.HealthKitSamplesUpdated += () => {
// Update UI as required
...
};
// Start session
HealthStore.StartWorkoutSession (workoutSession);
}
private void StartOutdoorRun ()
{
// Create a workout configuration
var workoutConfiguration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
// Start the session
StartWorkoutSession (workoutConfiguration);
}
#endregion
#region Override Methods
public override void HandleWorkoutConfiguration (HKWorkoutConfiguration workoutConfiguration)
{
// Start the session
StartWorkoutSession (workoutConfiguration);
}
#endregion
}
}
OutdoorRunDelegate.cs
Le OutdoorRunDelegate.cs
fichier de la version watchOS 3 de l’application d’entraînement inclut le code suivant :
using System;
using System.Collections.Generic;
using Foundation;
using WatchKit;
using HealthKit;
namespace MonkeyWorkout.MWWatchExtension
{
public class OutdoorRunDelegate : HKWorkoutSessionDelegate
{
#region Private Variables
private HKAnchoredObjectQuery QueryActiveEnergyBurned;
#endregion
#region Computed Properties
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
public float MilesRun { get; set; }
public double ActiveEnergyBurned { get; set;}
public List<HKWorkoutEvent> WorkoutEvents { get; set; } = new List<HKWorkoutEvent> ();
public List<HKSample> WorkoutSamples { get; set; } = new List<HKSample> ();
#endregion
#region Constructors
public OutdoorRunDelegate (HKHealthStore healthStore, HKWorkoutSession workoutSession)
{
// Initialize
this.HealthStore = healthStore;
this.WorkoutSession = workoutSession;
// Attach this delegate to the session
workoutSession.Delegate = this;
}
#endregion
#region Private Methods
private void ObserveHealthKitSamples ()
{
// Get the starting date of the required samples
var datePredicate = HKQuery.GetPredicateForSamples (WorkoutSession.StartDate, null, HKQueryOptions.StrictStartDate);
// Get data from the local device
var devices = new NSSet<HKDevice> (new HKDevice [] { HKDevice.LocalDevice });
var devicePredicate = HKQuery.GetPredicateForObjectsFromDevices (devices);
// Assemble compound predicate
var queryPredicate = NSCompoundPredicate.CreateAndPredicate (new NSPredicate [] { datePredicate, devicePredicate });
// Get ActiveEnergyBurned
QueryActiveEnergyBurned = new HKAnchoredObjectQuery (HKQuantityType.Create (HKQuantityTypeIdentifier.ActiveEnergyBurned), queryPredicate, null, HKSampleQuery.NoLimit, (query, addedObjects, deletedObjects, newAnchor, error) => {
// Valid?
if (error == null) {
// Yes, process all returned samples
foreach (HKSample sample in addedObjects) {
// Accumulate totals
var quantitySample = sample as HKQuantitySample;
ActiveEnergyBurned += quantitySample.Quantity.GetDoubleValue (HKUnit.Joule);
// Save samples
WorkoutSamples.Add (sample);
}
// Inform caller
RaiseHealthKitSamplesUpdated ();
}
});
// Start Query
HealthStore.ExecuteQuery (QueryActiveEnergyBurned);
}
private void StopObservingHealthKitSamples ()
{
// Stop query
HealthStore.StopQuery (QueryActiveEnergyBurned);
}
private void ResumeObservingHealthkitSamples ()
{
// Resume current queries
HealthStore.ExecuteQuery (QueryActiveEnergyBurned);
}
private void NotifyUserOfReachedMileGoal (float miles)
{
// Play haptic feedback
WKInterfaceDevice.CurrentDevice.PlayHaptic (WKHapticType.Notification);
// Raise event
RaiseReachedMileGoal (miles);
}
private void SaveWorkoutSession ()
{
// Build required workout quantities
var energyBurned = HKQuantity.FromQuantity (HKUnit.Joule, ActiveEnergyBurned);
var distance = HKQuantity.FromQuantity (HKUnit.Mile, MilesRun);
// Create any required metadata
var metadata = new NSMutableDictionary ();
metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));
// Create workout
var workout = HKWorkout.Create (HKWorkoutActivityType.Running,
WorkoutSession.StartDate,
NSDate.Now,
WorkoutEvents.ToArray (),
energyBurned,
distance,
metadata);
// Save to HealthKit
HealthStore.SaveObject (workout, (successful, error) => {
// Handle any errors
if (error == null) {
// Was the save successful
if (successful) {
// Add samples to workout
SaveWorkoutSamples (workout);
}
} else {
// Report error
...
}
});
}
private void SaveWorkoutSamples (HKWorkout workout)
{
// Add samples to saved workout
HealthStore.AddSamples (WorkoutSamples.ToArray (), workout, (success, error) => {
// Handle any errors
if (error == null) {
// Was the save successful
if (success) {
...
}
} else {
// Report error
...
}
});
}
#endregion
#region Public Methods
public void PauseWorkout ()
{
// Pause the current workout
HealthStore.PauseWorkoutSession (WorkoutSession);
}
public void ResumeWorkout ()
{
// Pause the current workout
HealthStore.ResumeWorkoutSession (WorkoutSession);
}
public void ReachedNextMile ()
{
// Create and save marker event
var markerEvent = HKWorkoutEvent.Create (HKWorkoutEventType.Marker, NSDate.Now);
WorkoutEvents.Add (markerEvent);
// Notify user
NotifyUserOfReachedMileGoal (++MilesRun);
}
public void EndOutdoorRun ()
{
// End the current workout session
HealthStore.EndWorkoutSession (WorkoutSession);
}
#endregion
#region Override Methods
public override void DidFail (HKWorkoutSession workoutSession, NSError error)
{
// Handle workout session failing
RaiseFailed ();
}
public override void DidChangeToState (HKWorkoutSession workoutSession, HKWorkoutSessionState toState, HKWorkoutSessionState fromState, NSDate date)
{
// Take action based on the change in state
switch (toState) {
case HKWorkoutSessionState.NotStarted:
break;
case HKWorkoutSessionState.Paused:
StopObservingHealthKitSamples ();
RaisePaused ();
break;
case HKWorkoutSessionState.Running:
if (fromState == HKWorkoutSessionState.Paused) {
ResumeObservingHealthkitSamples ();
} else {
ObserveHealthKitSamples ();
}
RaiseRunning ();
break;
case HKWorkoutSessionState.Ended:
StopObservingHealthKitSamples ();
SaveWorkoutSession ();
RaiseEnded ();
break;
}
}
public override void DidGenerateEvent (HKWorkoutSession workoutSession, HKWorkoutEvent @event)
{
base.DidGenerateEvent (workoutSession, @event);
// Save HealthKit generated event
WorkoutEvents.Add (@event);
// Take action based on the type of event
switch (@event.Type) {
case HKWorkoutEventType.Lap:
...
break;
case HKWorkoutEventType.Marker:
...
break;
case HKWorkoutEventType.MotionPaused:
...
break;
case HKWorkoutEventType.MotionResumed:
...
break;
case HKWorkoutEventType.Pause:
...
break;
case HKWorkoutEventType.Resume:
...
break;
}
}
#endregion
#region Events
public delegate void OutdoorRunEventDelegate ();
public delegate void OutdoorRunMileGoalDelegate (float miles);
public event OutdoorRunEventDelegate Failed;
internal void RaiseFailed ()
{
if (this.Failed != null) this.Failed ();
}
public event OutdoorRunEventDelegate Paused;
internal void RaisePaused ()
{
if (this.Paused != null) this.Paused ();
}
public event OutdoorRunEventDelegate Running;
internal void RaiseRunning ()
{
if (this.Running != null) this.Running ();
}
public event OutdoorRunEventDelegate Ended;
internal void RaiseEnded ()
{
if (this.Ended != null) this.Ended ();
}
public event OutdoorRunMileGoalDelegate ReachedMileGoal;
internal void RaiseReachedMileGoal (float miles)
{
if (this.ReachedMileGoal != null) this.ReachedMileGoal (miles);
}
public event OutdoorRunEventDelegate HealthKitSamplesUpdated;
internal void RaiseHealthKitSamplesUpdated ()
{
if (this.HealthKitSamplesUpdated != null) this.HealthKitSamplesUpdated ();
}
#endregion
}
}
Meilleures pratiques
Apple suggère d’utiliser les meilleures pratiques suivantes lors de la conception et de l’implémentation d’applications d’entraînement dans watchOS 3 et iOS 10 :
- Assurez-vous que l’application watchOS 3 Workout est toujours fonctionnelle même lorsqu’elle ne parvient pas à se connecter à l’i Téléphone et à la version iOS 10 de l’application.
- Utilisez la distance HealthKit lorsque le GPS n’est pas disponible, car il est en mesure de générer des échantillons de distance sans GPS.
- Autoriser l’utilisateur à démarrer l’entraînement à partir de l’Apple Watch ou de l’i Téléphone.
- Autoriser l’application à afficher des séances d’entraînement à partir d’autres sources (telles que d’autres applications tierces) dans ses vues de données historiques.
- Vérifiez que l’application n’affiche pas les séances d’entraînement supprimées dans les données historiques.
Résumé
Cet article a abordé les améliorations apportées par Apple aux applications d’entraînement dans watchOS 3 et comment les implémenter dans Xamarin.