watchOS Workout Apps in Xamarin
Dieser Artikel befasst sich mit den Verbesserungen, die Apple für das Training von Apps in WatchOS 3 gemacht hat und wie sie in Xamarin implementiert werden.
Neu bei watchOS 3 haben trainingsbezogene Apps die Möglichkeit, im Hintergrund auf der Apple Watch auszuführen und Zugriff auf HealthKit-Daten zu erhalten. Die übergeordnete iOS 10-basierte App verfügt auch über die Möglichkeit, die watchOS 3-basierte App ohne Benutzereingriff zu starten.
Die folgenden Themen werden im Detail behandelt:
Informationen zu Workout-Apps
Benutzer von Fitness- und Workout-Apps können sehr engagiert sein, um mehrere Stunden des Tages auf ihre Gesundheits- und Fitnessziele hin zu devozieren. Daher erwarten sie reaktionsfähige, benutzerfreundliche Apps, die Daten genau sammeln und anzeigen und nahtlos in Apple Health integrieren.
Eine gut gestaltete Fitness- oder Workout-App hilft Benutzern, ihre Aktivitäten zu erstellen, um ihre Fitnessziele zu erreichen. Mit der Apple Watch haben Fitness- und Workout-Apps sofortigen Zugriff auf Herzfrequenz, Kalorienverbrennung und Aktivitätserkennung.
Neu bei watchOS 3 bietet Background Running trainingsbezogene Apps die Möglichkeit, im Hintergrund auf der Apple Watch auszuführen und Zugriff auf HealthKit-Daten zu erhalten.
In diesem Dokument wird das Feature "Hintergrundausführung" vorgestellt, der Lebenszyklus der Workout-App behandelt und gezeigt, wie eine Workout-App zu den Aktivitätsringen des Benutzers auf der Apple Watch beitragen kann.
Informationen zu Trainingssitzungen
Das Herzstück jeder Workout-App ist eine Workout Session (HKWorkoutSession
), die der Benutzer starten und beenden kann. Die Trainingssitzungs-API ist einfach zu implementieren und bietet verschiedene Vorteile für eine Workout-App, z. B.:
- Bewegungs- und Kalorienverbrennungserkennung basierend auf dem Aktivitätstyp.
- Automatischer Beitrag zu den Aktivitätsringen des Benutzers.
- Während einer Sitzung wird die App automatisch angezeigt, wenn der Benutzer das Gerät reaktiviert (entweder durch Auslösen des Handgelenks oder Interagieren mit der Apple Watch).
Informationen zum Ausführen des Hintergrunds
Wie bereits erwähnt, kann mit watchOS 3 eine Workout-App so eingestellt werden, dass sie im Hintergrund ausgeführt wird. Die Verwendung von Hintergrundausführung einer Workout-App kann Daten aus den Apple Watch-Sensoren verarbeiten, während sie im Hintergrund ausgeführt werden. Beispielsweise kann eine App weiterhin die Herzfrequenz des Benutzers überwachen, auch wenn sie nicht mehr auf dem Bildschirm angezeigt wird.
Hintergrundausführung bietet auch die Möglichkeit, dem Benutzer während einer aktiven Workout-Sitzung jederzeit Livefeedback zu präsentieren, z. B. das Senden einer haptischen Warnung, um den Benutzer über seinen aktuellen Fortschritt zu informieren.
Darüber hinaus ermöglicht die Hintergrundausführung der App, die Benutzeroberfläche schnell zu aktualisieren, sodass der Benutzer über die neuesten Daten verfügt, wenn er schnell auf die Apple Watch blicken kann.
Um eine hohe Leistung bei Apple Watch zu Standard, sollte eine Watch-App mit Hintergrundausführung die Menge der Hintergrundarbeit einschränken, um Akku zu sparen. Wenn eine App im Hintergrund eine übermäßige CPU verwendet, kann sie von watchOS angehalten werden.
Aktivieren der Ausführung des Hintergrunds
Gehen Sie wie folgt vor, um die Hintergrundausführung zu aktivieren:
Doppelklicken Sie im Projektmappen-Explorer auf die Begleitdatei der Watch-Erweiterung i Telefon,
Info.plist
um sie zur Bearbeitung zu öffnen.Wechseln Sie zur Ansicht Quelle:
Fügen Sie einen neuen Schlüssel hinzu, der aufgerufen wird
WKBackgroundModes
, und legen Sie den Typ auf :Array
Hinzufügen eines neuen Elements zum Array mit dem Typ von
String
und einem Wert vonworkout-processing
:Speichern Sie die Änderungen in der Datei.
Starten einer Trainingssitzung
Es gibt drei Standard Schritte zum Starten einer Workout-Sitzung:
- Die App muss die Autorisierung für den Zugriff auf Daten in HealthKit anfordern.
- Erstellen Sie ein Workout Configuration-Objekt für die Art des Trainings, das gestartet wird.
- Erstellen und starten Sie eine Workout-Sitzung mithilfe der neu erstellten Workout-Konfiguration.
Anfordern einer Autorisierung
Bevor eine App auf die HealthKit-Daten des Benutzers zugreifen kann, muss sie vom Benutzer eine Autorisierung anfordern und empfangen. Je nach Art der Workout-App können die folgenden Arten von Anforderungen vorgenommen werden:
- Autorisierung zum Schreiben von Daten:
- Training
- Autorisierung zum Lesen von Daten:
- Energie verbrannt
- Distanz
- Herzfrequenz
Bevor eine App die Autorisierung anfordern kann, muss sie für den Zugriff auf HealthKit konfiguriert werden.
Gehen Sie folgendermaßen vor:
Doppelklicken Sie im Projektmappen-Explorer auf die Datei
Entitlements.plist
, um sie zur Bearbeitung zu öffnen.Scrollen Sie nach unten, und aktivieren Sie "HealthKit aktivieren":
Speichern Sie die Änderungen in der Datei.
Befolgen Sie die Anweisungen in den Abschnitten "Explizite App-ID und Bereitstellungsprofil" und "Zuordnen der App-ID und des Bereitstellungsprofils mit Ihrer Xamarin.iOS-App" des Artikels "Einführung in HealthKit", um die App ordnungsgemäß bereitzustellen.
Verwenden Sie schließlich die Anweisungen im Programming Health Kit und Anfordern von Berechtigungen aus den Abschnitten "Benutzer " des Artikels "Einführung in HealthKit ", um die Autorisierung für den Zugriff auf den HealthKit-Datenspeicher des Benutzers anzufordern.
Festlegen der Workout-Konfiguration
Trainingssitzungen werden mit einem Workout Configuration -Objekt (HKWorkoutConfiguration
) erstellt, das den Trainingstyp (z HKWorkoutActivityType.Running
. B. ) und den Trainingsort (z HKWorkoutSessionLocationType.Outdoor
. B. ):
using HealthKit;
...
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
Erstellen eines Trainingssitzungsdelegats
Um die Ereignisse zu behandeln, die während einer Workout-Sitzung auftreten können, muss die App eine Workout Session Delegate-Instanz erstellen. Fügen Sie dem Projekt eine neue Klasse hinzu, und stützen Sie sie aus der HKWorkoutSessionDelegate
Klasse. Für das Beispiel eines Outdoor-Laufs könnte es wie folgt aussehen:
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
}
}
Diese Klasse erstellt mehrere Ereignisse, die ausgelöst werden, wenn sich der Zustand der Workout Session ändert (DidChangeToState
) und wenn die Workout Session fehlschlägt (DidFail
).
Erstellen einer Trainingssitzung
Verwenden sie die oben erstellte Workout Configuration and Workout Session Delegate, um eine neue Workout-Sitzung zu erstellen und mit dem Standardmäßigen HealthKit-Speicher des Benutzers zu starten:
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);
}
Wenn die App diese Workout-Sitzung startet und der Benutzer wieder zur Watch-Gesicht wechselt, wird über dem Gesicht ein winziges grünes "Running Man"-Symbol angezeigt:
Wenn der Benutzer auf dieses Symbol tippt, wird er zurück zur App weitergeleitet.
Datensammlung und -kontrolle
Nachdem eine Workout-Sitzung konfiguriert und gestartet wurde, muss die App Daten über die Sitzung (z. B. die Herzfrequenz des Benutzers) sammeln und den Zustand der Sitzung steuern:
- Observing Samples – Die App muss Informationen aus HealthKit abrufen, die für den Benutzer ausgeführt und angezeigt werden.
- Observing Events – Die App muss auf Ereignisse reagieren, die von HealthKit oder von der Benutzeroberfläche der App generiert werden (z. B. der Benutzer hält das Training an).
- Eingabe des Ausführungszustands – Die Sitzung wurde gestartet und wird zurzeit ausgeführt.
- Geben Sie den Angehaltenen Zustand ein – Der Benutzer hat die aktuelle Trainingssitzung angehalten und kann es zu einem späteren Zeitpunkt neu starten. Der Benutzer kann mehrmals in einer einzigen Workout-Sitzung zwischen dem Laufenden und dem angehaltenen Zustand wechseln.
- Training beenden - An jedem Punkt kann der Benutzer die Workout-Sitzung beenden oder es kann selbst ablaufen und enden, wenn es ein getaktetes Training war (z. B. eine Zwei-Meilen-Lauf).
Der letzte Schritt besteht darin, die Ergebnisse der Workout-Sitzung im HealthKit-Datenspeicher des Benutzers zu speichern.
Observing HealthKit Samples
Die App muss eine Anchor-Objektabfrage für jede der HealthKit-Datenpunkte öffnen, an denen sie interessiert ist, z. B. Herzfrequenz oder aktive Energie, die verbrannt wird. Für jeden beobachteten Datenpunkt muss ein Updatehandler erstellt werden, um neue Daten zu erfassen, sobald er an die App gesendet wird.
Aus diesen Datenpunkten kann die App Summen sammeln (z. B. die Gesamtlaufzeit) und die Benutzeroberfläche nach Bedarf aktualisieren. Darüber hinaus kann die App Benutzer benachrichtigen, wenn sie ein bestimmtes Ziel oder eine bestimmte Leistung erreicht haben, z. B. das Abschließen der nächsten Meile einer Ausführung.
Sehen Sie sich den folgenden Beispielcode an:
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);
}
Es erstellt ein Prädikat, um das Startdatum festzulegen, das daten für die Verwendung der GetPredicateForSamples
Methode abgerufen werden soll. Es erstellt eine Reihe von Geräten, um HealthKit-Informationen mithilfe der GetPredicateForObjectsFromDevices
Methode abzurufen, in diesem Fall nur die lokale Apple Watch (HKDevice.LocalDevice
). Die beiden Prädikate werden mithilfe der CreateAndPredicate
Methode in ein Zusammengesetztes PrädikatNSCompoundPredicate
() kombiniert.
Für HKAnchoredObjectQuery
den gewünschten Datenpunkt (in diesem Fall HKQuantityTypeIdentifier.ActiveEnergyBurned
für den Active Energy Burned-Datenpunkt) wird kein Grenzwert für die zurückgegebeneHKSampleQuery.NoLimit
Datenmenge () festgelegt, und ein Updatehandler wird definiert, um Daten zu verarbeiten, die von HealthKit an die App zurückgegeben werden.
Der Updatehandler wird jedes Mal aufgerufen, wenn neue Daten für den angegebenen Datenpunkt an die App übermittelt werden. Wenn kein Fehler zurückgegeben wird, kann die App die Daten sicher lesen, erforderliche Berechnungen vornehmen und die Benutzeroberfläche nach Bedarf aktualisieren.
Der Code durchläuft alle im Array zurückgegebenen Beispiele (HKSample
) und wandelt sie in ein Mengenbeispiel (HKQuantitySample
).addedObjects
Anschließend erhält sie den doppelten Wert der Probe als Joule (HKUnit.Joule
) und sammelt sie in der laufenden Gesamtmenge der aktiven Energie, die für das Training verbrannt wird, und aktualisiert die Benutzeroberfläche.
Zielbenachrichtigung erreicht
Wie oben Erwähnung, kann der Benutzer haptisches Feedback über das Taptic Engine senden, wenn der Benutzer ein Ziel in der Workout-App (z. B. die erste Meile eines Laufs) erreicht. Die App sollte an diesem Punkt auch die Benutzeroberfläche aktualisieren, da der Benutzer sein Handgelenk mehr als wahrscheinlich auslöst, um das Ereignis zu sehen, das das Feedback angezeigt hat.
Verwenden Sie den folgenden Code, um das haptische Feedback wiederzugeben:
// Play haptic feedback
WKInterfaceDevice.CurrentDevice.PlayHaptic (WKHapticType.Notification);
Beobachten von Ereignissen
Ereignisse sind Zeitstempel, mit denen die App bestimmte Punkte während des Trainings des Benutzers hervorheben kann. Einige Ereignisse werden direkt von der App erstellt und im Training gespeichert, und einige Ereignisse werden automatisch von HealthKit erstellt.
Um Ereignisse zu beobachten, die von HealthKit erstellt werden, überschreibt die App die DidGenerateEvent
Methode der 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 hat die folgenden neuen Ereignistypen in watchOS 3 hinzugefügt:
HKWorkoutEventType.Lap
- Sind für Ereignisse, die das Training in gleiche Entfernungsabschnitte unterteilen. Beispielsweise zum Markieren einer Runde um eine Strecke während des Laufens.HKWorkoutEventType.Marker
- Sind für beliebige Interessante Punkte innerhalb des Workouts. Beispielsweise erreichen Sie einen bestimmten Punkt auf der Route eines Outdoor-Laufs.
Diese neuen Typen können von der App erstellt und im Training gespeichert werden, um später diagramme und Statistiken zu erstellen.
Gehen Sie wie folgt vor, um ein Marker-Ereignis zu erstellen:
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);
}
Dieser Code erstellt eine neue Instanz eines Marker-Ereignisses (HKWorkoutEvent
) und speichert sie in einer privaten Auflistung von Ereignissen (die später in die Workout-Sitzung geschrieben werden) und benachrichtigt den Benutzer des Ereignisses über Haptik.
Anhalten und Fortsetzen von Trainingseinheiten
An jedem Punkt in einer Trainingssitzung kann der Benutzer das Training vorübergehend anhalten und zu einem späteren Zeitpunkt fortsetzen. Sie können z. B. einen Innenlauf anhalten, um einen wichtigen Anruf zu führen und die Ausführung fortzusetzen, nachdem der Anruf abgeschlossen wurde.
Die Benutzeroberfläche der App sollte eine Möglichkeit bieten, das Training anzuhalten und fortzusetzen (durch Aufrufen von HealthKit), damit die Apple Watch sowohl Energie als auch Datenraum sparen kann, während der Benutzer seine Aktivität angehalten hat. Außerdem sollte die App alle neuen Datenpunkte ignorieren, die möglicherweise empfangen werden, wenn die Workout-Sitzung in einem angehaltenen Zustand ist.
HealthKit reagiert auf Pausen- und Fortsetzungsaufrufe, indem Pause- und Resume-Ereignisse generiert werden. Während die Workout-Sitzung angehalten wird, werden keine neuen Ereignisse oder Daten von HealthKit an die App gesendet, bis die Sitzung fortgesetzt wird.
Verwenden Sie den folgenden Code, um eine Workout-Sitzung anzuhalten und fortzusetzen:
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);
}
Die Ereignisse Pause und Resume, die aus HealthKit generiert werden, können behandelt werden, indem die DidGenerateEvent
Methode der 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;
}
}
Bewegungsereignisse
Neu bei watchOS 3 sind die Ereignisse "Motion Paused" (HKWorkoutEventType.MotionPaused
) und "Motion Resumed()" (HKWorkoutEventType.MotionResumed
). Diese Ereignisse werden automatisch von HealthKit während eines laufenden Trainings ausgelöst, wenn der Benutzer beginnt und anhält, sich zu bewegen.
Wenn die App ein Motion Paused-Ereignis empfängt, sollte das Sammeln von Daten beendet werden, bis der Benutzer die Bewegung fortsetzt und das Motion Resumes-Ereignis empfangen wird. Die App sollte die Workout-Sitzung nicht als Reaktion auf ein Angehaltenes Motion-Ereignis anhalten.
Wichtig
Die Ereignisse "Motion Paused" und "Motion Resume" werden nur für den RunningWorkout-Aktivitätstyp (HKWorkoutActivityType.Running
) unterstützt.
Auch hier können diese Ereignisse behandelt werden, indem die DidGenerateEvent
Methode der 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;
}
}
Beenden und Speichern der Trainingssitzung
Wenn der Benutzer sein Training abgeschlossen hat, muss die App die aktuelle Workout-Sitzung beenden und in der HealthKit-Datenbank speichern. In HealthKit gespeicherte Workouts werden automatisch in der Trainingsaktivitätsliste angezeigt.
Neu bei iOS 10, enthält dies auch die Liste der Workout-Aktivitätslisten auf dem i Telefon des Benutzers. Auch wenn die Apple Watch nicht in der Nähe ist, wird das Training auf dem Telefon präsentiert.
Trainingseinheiten, die Energy Samples enthalten, werden den Move Ring des Benutzers in der Aktivitäten-App aktualisieren, sodass Apps von Drittanbietern jetzt zu den täglichen Move-Zielen des Benutzers beitragen können.
Die folgenden Schritte sind erforderlich, um eine Workout-Sitzung zu beenden und zu speichern:
- Zunächst muss die App die Workout-Sitzung beenden.
- Die Workout-Sitzung wird in HealthKit gespeichert.
- Fügen Sie der gespeicherten Workout-Sitzung proben (z. B. Energie verbrannt oder Entfernung) hinzu.
Beenden der Sitzung
Rufen Sie zum Beenden der Workout-Sitzung die EndWorkoutSession
Methode der HKHealthStore
Übergabe der HKWorkoutSession
:
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
...
public void EndOutdoorRun ()
{
// End the current workout session
HealthStore.EndWorkoutSession (WorkoutSession);
}
Dadurch werden die Gerätesensoren auf ihren normalen Modus zurückgesetzt. Wenn HealthKit das Training beendet hat, erhält es einen Rückruf an die DidChangeToState
Methode der 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;
}
}
Speichern der Sitzung
Nachdem die App die Workout-Sitzung beendet hat, muss sie ein Workout (HKWorkout
) erstellen und (zusammen mit ereignissen) im HealthKit-Datenspeicher speichern (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
}
});
}
Mit diesem Code wird die Gesamtmenge an Energie erzeugt, die für das Training als HKQuantity
Objekte verbraucht und entfernt wird. Ein Wörterbuch mit Metadaten, die das Training definieren, wird erstellt, und der Ort des Workouts wird angegeben:
metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));
Ein neues HKWorkout
Objekt wird mit demselben HKWorkoutActivityType
wie das HKWorkoutSession
Start- und Enddatum, die Liste der Ereignisse (die aus den abschnitten oben gesammelt werden), die Energie verbrannt, die Gesamtdistanz und das Metadatenwörterbuch erstellt. Dieses Objekt wird im Integritätsspeicher und allen behandelten Fehlern gespeichert.
Hinzufügen von Beispielen
Wenn die App eine Reihe von Beispielen in einem Workout speichert, generiert HealthKit eine Verbindung zwischen den Beispielen und dem Workout selbst, damit die App HealthKit zu einem späteren Zeitpunkt für alle Mit einem bestimmten Training verknüpften Beispiele abfragen kann. Mithilfe dieser Informationen kann die App Diagramme aus den Trainingsdaten generieren und mit einem Training Zeitleiste zeichnen.
Damit eine App zum Move Ring der Aktivitäts-App beiträgt, muss sie Energiebeispiele mit dem gespeicherten Training enthalten. Darüber hinaus müssen die Gesamtsummen für Entfernung und Energie mit der Summe aller Proben übereinstimmen, die die App einem gespeicherten Training ordnet.
Gehen Sie wie folgt vor, um Beispielen zu einem gespeicherten Training hinzuzufügen:
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
}
});
}
Optional kann die App eine kleinere Teilmenge von Beispielen oder ein Megabeispiel (über den gesamten Trainingsbereich) berechnen und erstellen, der dann mit dem gespeicherten Training verknüpft wird.
Training und iOS 10
Jede WatchOS 3-Training-App verfügt über eine übergeordnete iOS 10-basierte Workout-App und kann, neu bei iOS 10, verwendet werden, um ein Training zu starten, das die Apple Watch im Workout-Modus (ohne Benutzereingriff) platziert und die watchOS-App im Hintergrundausführungsmodus ausführt (weitere Details finden Sie weiter oben unter "Informationen zum Ausführen im Hintergrund ").
Während die WatchOS-App ausgeführt wird, kann sie Watch Verbinden ivity für Messaging und Kommunikation mit der übergeordneten iOS-App verwenden.
Sehen Sie sich an, wie dieser Prozess funktioniert:
- Die i Telefon-App erstellt ein
HKWorkoutConfiguration
Objekt und legt den Trainingstyp und den Standort fest. - Das
HKWorkoutConfiguration
Objekt wird die Apple Watch-Version der App gesendet und wird, wenn es noch nicht ausgeführt wird, vom System gestartet. - Mit der übergebenen Workout-Konfiguration startet die watchOS 3-App eine neue Workout Session (
HKWorkoutSession
).
Wichtig
Damit die übergeordnete i Telefon-App ein Training auf der Apple Watch starten kann, muss die WatchOS 3-App hintergrundausführung aktiviert sein. Weitere Details finden Sie weiter oben unter Aktivieren der Hintergrundausführung .
Dieser Prozess ähnelt dem Prozess des direkten Startens einer Workout Session in der watchOS 3-App. Verwenden Sie auf dem i Telefon den folgenden Code:
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
...
}
});
}
}
Dieser Code stellt sicher, dass die WatchOS-Version der App installiert ist und die i Telefon-Version zuerst eine Verbindung mit der App herstellen kann:
if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
...
}
Anschließend erstellt es eine HKWorkoutConfiguration
wie gewohnt und verwendet die StartWatchApp
Methode, HKHealthStore
um sie an die Apple Watch zu senden und die App und die Workout Session zu starten.
Verwenden Sie auf der Watch OS-App den folgenden Code in der 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);
}
Es verwendet und HKWorkoutConfiguration
erstellt eine neue HKWorkoutSession
und fügt eine Instanz der benutzerdefinierten HKWorkoutSessionDelegate
an. Die Trainingssitzung wird für den HealthKit HealthKit Store des Benutzers gestartet.
Zusammenführen aller Teile
Eine watchOS 3-basierte Workout-App und ihre übergeordnete iOS 10-basierte Workout-App können die folgenden Teile umfassen:
- iOS 10
ViewController.cs
– Behandelt den Start einer Watch Verbinden ivity Session und ein Training auf der Apple Watch. - watchOS 3
ExtensionDelegate.cs
– Behandelt die watchOS 3-Version der Workout-App. - watchOS 3
OutdoorRunDelegate.cs
– Eine benutzerdefinierteHKWorkoutSessionDelegate
Behandlung von Ereignissen für das Training.
Wichtig
Der in den folgenden Abschnitten gezeigte Code enthält nur die Teile, die erforderlich sind, um die neuen, erweiterten Features zu implementieren, die für Workout-Apps in watchOS 3 bereitgestellt werden. Alle unterstützenden Code und der Code zum Präsentieren und Aktualisieren der Benutzeroberfläche sind nicht enthalten, können aber einfach erstellt werden, indem sie unsere andere WatchOS-Dokumentation folgen.
ViewController.cs
Die ViewController.cs
Datei in der übergeordneten iOS 10-Version der Workout-App würde den folgenden Code enthalten:
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
Die ExtensionDelegate.cs
Datei in der WatchOS 3-Version der Workout-App würde den folgenden Code enthalten:
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
Die OutdoorRunDelegate.cs
Datei in der WatchOS 3-Version der Workout-App würde den folgenden Code enthalten:
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
}
}
Bewährte Methoden
Apple schlägt vor, beim Entwerfen und Implementieren von Workout-Apps in watchOS 3 und iOS 10 die folgenden bewährten Methoden zu verwenden:
- Stellen Sie sicher, dass die WatchOS 3 Workout-App auch dann weiterhin funktionsfähig ist, wenn sie keine Verbindung mit dem i Telefon und der iOS 10-Version der App herstellen kann.
- Verwenden Sie HealthKit-Entfernung, wenn GPS nicht verfügbar ist, da sie Entfernungsproben ohne GPS generieren kann.
- Ermöglichen Sie dem Benutzer, das Training entweder von der Apple Watch oder der i Telefon aus zu starten.
- Ermöglichen Sie der App, Trainings aus anderen Quellen (z. B. andere Drittanbieter-Apps) in ihren historischen Datenansichten anzuzeigen.
- Stellen Sie sicher, dass die App gelöschte Workouts nicht in historischen Daten anzeigt.
Zusammenfassung
In diesem Artikel wurden die Verbesserungen behandelt, die Apple für Training-Apps in watchOS 3 gemacht hat und wie sie in Xamarin implementiert werden.