Freigeben über


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.

Beispiel für Fitness- und Training-App

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:

  1. Doppelklicken Sie im Projektmappen-Explorer auf die Begleitdatei der Watch-Erweiterung i Telefon, Info.plist um sie zur Bearbeitung zu öffnen.

  2. Wechseln Sie zur Ansicht Quelle:

    Die Quellansicht

  3. Fügen Sie einen neuen Schlüssel hinzu, der aufgerufen wirdWKBackgroundModes, und legen Sie den Typ auf :Array

    Hinzufügen eines neuen Schlüssels namens WKBackgroundModes

  4. Hinzufügen eines neuen Elements zum Array mit dem Typ von String und einem Wert von workout-processing:

    Hinzufügen eines neuen Elements zum Array mit dem Typ der Zeichenfolge und dem Wert der Trainingsverarbeitung

  5. Speichern Sie die Änderungen in der Datei.

Starten einer Trainingssitzung

Es gibt drei Standard Schritte zum Starten einer Workout-Sitzung:

Die drei Standard Schritte zum Starten einer Workout-Sitzung

  1. Die App muss die Autorisierung für den Zugriff auf Daten in HealthKit anfordern.
  2. Erstellen Sie ein Workout Configuration-Objekt für die Art des Trainings, das gestartet wird.
  3. 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:

  1. Doppelklicken Sie im Projektmappen-Explorer auf die Datei Entitlements.plist, um sie zur Bearbeitung zu öffnen.

  2. Scrollen Sie nach unten, und aktivieren Sie "HealthKit aktivieren":

    Aktivieren von HealthKit

  3. Speichern Sie die Änderungen in der Datei.

  4. 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.

  5. 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:

Ein kleines grünes Mannsymbol, das über dem Gesicht angezeigt wird

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:

Datensammlung und Steuerelementdiagramm

  1. Observing Samples – Die App muss Informationen aus HealthKit abrufen, die für den Benutzer ausgeführt und angezeigt werden.
  2. 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).
  3. Eingabe des Ausführungszustands – Die Sitzung wurde gestartet und wird zurzeit ausgeführt.
  4. 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.
  5. 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:

Beenden und Speichern des Trainingssitzungsdiagramms

  1. Zunächst muss die App die Workout-Sitzung beenden.
  2. Die Workout-Sitzung wird in HealthKit gespeichert.
  3. 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 HKWorkoutSessionStart- 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:

i Telefon- und Apple Watch-Kommunikationsdiagramm

  1. Die i Telefon-App erstellt ein HKWorkoutConfiguration Objekt und legt den Trainingstyp und den Standort fest.
  2. Das HKWorkoutConfiguration Objekt wird die Apple Watch-Version der App gesendet und wird, wenn es noch nicht ausgeführt wird, vom System gestartet.
  3. 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 HKWorkoutSessionDelegatean. 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:

  1. iOS 10 ViewController.cs – Behandelt den Start einer Watch Verbinden ivity Session und ein Training auf der Apple Watch.
  2. watchOS 3 ExtensionDelegate.cs – Behandelt die watchOS 3-Version der Workout-App.
  3. watchOS 3 OutdoorRunDelegate.cs – Eine benutzerdefinierte HKWorkoutSessionDelegate 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.