Condividi tramite


watchOS Workout Apps in Xamarin

Questo articolo illustra i miglioramenti apportati da Apple alle app di allenamento in watchOS 3 e come implementarle in Xamarin.

Novità di watchOS 3, le app correlate all'allenamento hanno la possibilità di eseguire in background su Apple Watch e ottenere l'accesso ai dati di HealthKit. La loro app basata su iOS 10 padre ha anche la possibilità di avviare l'app basata su watchOS 3 senza l'intervento dell'utente.

Gli argomenti seguenti vengono descritti nel dettaglio:

Informazioni sulle app di allenamento

Gli utenti di app di fitness e allenamento possono essere altamente dedicati, dedicando diverse ore del giorno verso i loro obiettivi di salute e fitness. Di conseguenza, si aspettano app reattive e facili da usare che raccolgono e visualizzano in modo accurato i dati e si integrano perfettamente con Apple Health.

Un'app di fitness o allenamento ben progettata consente agli utenti di tracciare le loro attività per raggiungere i loro obiettivi di fitness. Usando Apple Watch, le app di fitness e allenamento hanno accesso immediato alla frequenza cardiaca, al consumo di calorie e al rilevamento delle attività.

Esempio di app fitness e allenamento

Novità di watchOS 3, Background Running offre alle app correlate all'allenamento la possibilità di eseguire in background su Apple Watch e ottenere l'accesso ai dati di HealthKit.

Questo documento presenta la funzionalità Background Running, illustra il ciclo di vita dell'app di allenamento e mostra come un'app di allenamento può contribuire agli anelli di attività dell'utente su Apple Watch.

Informazioni sulle sessioni di allenamento

Il cuore di ogni app di allenamento è una sessione di allenamento (HKWorkoutSession) che l'utente può iniziare e arrestare. L'API Sessione di allenamento è facile da implementare e offre diversi vantaggi per un'app di allenamento, ad esempio:

  • Rilevamento del movimento e della bruciare calorie in base al tipo di attività.
  • Contributo automatico agli anelli attività dell'utente.
  • Durante una sessione, l'app verrà visualizzata automaticamente ogni volta che l'utente riattiva il dispositivo (alzando il polso o interagendo con Apple Watch).

Informazioni sull'esecuzione in background

Come indicato in precedenza, con watchOS 3 un'app di allenamento può essere impostata per l'esecuzione in background. Usando Background Running un'app di allenamento può elaborare i dati dai sensori di Apple Watch durante l'esecuzione in background. Ad esempio, un'app può continuare a monitorare la frequenza cardiaca dell'utente, anche se non viene più visualizzata sullo schermo.

Background Running offre inoltre la possibilità di presentare feedback live all'utente in qualsiasi momento durante una sessione di allenamento attiva, ad esempio l'invio di un avviso aptico per informare l'utente dello stato di avanzamento corrente.

Inoltre, Background Running consente all'app di aggiornare rapidamente l'interfaccia utente in modo che l'utente abbia i dati più recenti quando guarda rapidamente Apple Watch.

Per mantenere prestazioni elevate in Apple Watch, un'app watch che usa Background Running deve limitare la quantità di lavoro in background per risparmiare batteria. Se un'app usa una CPU eccessiva in background, può essere sospesa da watchOS.

Abilitazione dell'esecuzione in background

Per abilitare l'esecuzione in background, eseguire le operazioni seguenti:

  1. Nel Esplora soluzioni fare doppio clic sul file dell'app complementare i Telefono dell'app Info.plist watch per aprirlo per la modifica.

  2. Passare alla visualizzazione Origine:

    Visualizzazione Origine

  3. Aggiungere una nuova chiave denominata WKBackgroundModes e impostare Type su Array:

    Aggiungere una nuova chiave denominata WKBackgroundModes

  4. Aggiungere un nuovo elemento alla matrice con il tipo di String e il valore :workout-processing

    Aggiungere un nuovo elemento alla matrice con il tipo di stringa e il valore di elaborazione dell'allenamento

  5. Salvare le modifiche apportate al file.

Avvio di una sessione di allenamento

Ci sono tre passaggi principali per avviare una sessione di allenamento:

I tre passaggi principali per avviare una sessione di allenamento

  1. L'app deve richiedere l'autorizzazione per accedere ai dati in HealthKit.
  2. Creare un oggetto Configurazione allenamento per il tipo di allenamento avviato.
  3. Creare e avviare una sessione di allenamento usando la configurazione di allenamento appena creata.

Richiesta di autorizzazione

Prima che un'app possa accedere ai dati healthkit dell'utente, deve richiedere e ricevere l'autorizzazione dall'utente. A seconda della natura dell'app di allenamento, potrebbe effettuare i tipi di richieste seguenti:

  • Autorizzazione alla scrittura dei dati:
    • Allenamenti
  • Autorizzazione alla lettura dei dati:
    • Energia bruciata
    • Distanza
    • Frequenza cardiaca

Prima che un'app possa richiedere l'autorizzazione, deve essere configurata per accedere a HealthKit.

Effettua le operazioni seguenti:

  1. In Esplora soluzioni fare doppio clic sul file Entitlements.plist per aprirlo e modificarlo.

  2. Scorrere fino alla fine e selezionare Abilita HealthKit:

    Selezionare Enable HealthKit (Abilita HealthKit)

  3. Salvare le modifiche apportate al file.

  4. Seguire le istruzioni riportate nelle sezioni ID app esplicito e Profilo di provisioning e associazione dell'ID app e del profilo di provisioning con l'app Xamarin.iOS dell'articolo Introduzione a HealthKit per eseguire correttamente il provisioning dell'app.

  5. Infine, usare le istruzioni contenute nelle sezioni Programming Health Kit e Requesting Permission From the User ( Introduzione a HealthKit) dell'articolo Introduzione a HealthKit per richiedere l'autorizzazione per accedere all'archivio dati HealthKit dell'utente.

Impostazione della configurazione dell'allenamento

Le sessioni di allenamento vengono create usando un oggetto Training Configuration (HKWorkoutConfiguration) che specifica il tipo di allenamento (ad esempio HKWorkoutActivityType.Running) e la posizione di allenamento (ad esempio HKWorkoutSessionLocationType.Outdoor):

using HealthKit;
...

// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
  ActivityType = HKWorkoutActivityType.Running,
  LocationType = HKWorkoutSessionLocationType.Outdoor
};

Creazione di un delegato di sessione di allenamento

Per gestire gli eventi che possono verificarsi durante una sessione di allenamento, l'app dovrà creare un'istanza del delegato sessione di allenamento. Aggiungere una nuova classe al progetto e basarla dalla HKWorkoutSessionDelegate classe . Per l'esempio di esecuzione all'aperto, potrebbe essere simile al seguente:

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
  }
}

Questa classe crea diversi eventi che verranno generati come lo stato della sessione di allenamento cambia (DidChangeToState) e se la sessione di allenamento ha esito negativo (DidFail).

Creazione di una sessione di allenamento

Usando la configurazione allenamento e il delegato sessione di allenamento creato in precedenza per creare una nuova sessione di allenamento e avviarla con l'archivio HealthKit predefinito dell'utente:

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);
}

Se l'app avvia questa sessione di allenamento e l'utente torna al viso dell'orologio, verrà visualizzata un'icona "man in esecuzione" piccolo verde sopra il viso:

Icona di un piccolo uomo in esecuzione verde visualizzato sopra il viso

Se l'utente tocca questa icona, verrà nuovamente visualizzata nell'app.

Raccolta e controllo dei dati

Dopo aver configurato e avviato una sessione di allenamento, l'app dovrà raccogliere dati sulla sessione (ad esempio la frequenza cardiaca dell'utente) e controllare lo stato della sessione:

Diagramma di raccolta e controllo dei dati

  1. Osservazione di esempi: l'app dovrà recuperare informazioni da HealthKit su cui verrà eseguita e visualizzata all'utente.
  2. Osservazione degli eventi : l'app dovrà rispondere agli eventi generati da HealthKit o dall'interfaccia utente dell'app (ad esempio l'utente che sospendo l'allenamento).
  3. Immettere Stato di esecuzione: la sessione è stata avviata ed è attualmente in esecuzione.
  4. Immettere Stato sospeso - L'utente ha sospeso la sessione di allenamento corrente e può riavviarlo in un secondo momento. L'utente può passare tra gli stati in esecuzione e sospeso più volte in una singola sessione di allenamento.
  5. End Workout Session - A qualsiasi punto l'utente può terminare la sessione di allenamento o può scadere e terminare autonomamente se si tratta di un allenamento a consumo (ad esempio una corsa di due miglia).

Il passaggio finale consiste nel salvare i risultati della sessione di allenamento nell'archivio dati HealthKit dell'utente.

Osservazione degli esempi di HealthKit

L'app dovrà aprire una query dell'oggetto di ancoraggio per ognuno dei punti dati HealthKit a cui è interessato, ad esempio la frequenza cardiaca o l'energia attiva bruciata. Per ogni punto dati osservato, è necessario creare un gestore di aggiornamento per acquisire nuovi dati man mano che vengono inviati all'app.

Da questi punti dati, l'app può accumulare totali (ad esempio la distanza totale di esecuzione) e aggiornarla in base alle esigenze. Inoltre, l'app può notificare agli utenti quando hanno raggiunto un obiettivo o un raggiungimento specifico, ad esempio il completamento del miglio successivo di una corsa.

Esaminare il codice di esempio seguente:

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);

}

Crea un predicato per impostare la data di inizio che vuole ottenere i dati per l'uso del GetPredicateForSamples metodo . Crea un set di dispositivi per eseguire il pull delle informazioni di HealthKit usando il GetPredicateForObjectsFromDevices metodo , in questo caso solo Apple Watch locale (HKDevice.LocalDevice). I due predicati vengono combinati in un predicato composto (NSCompoundPredicate) usando il CreateAndPredicate metodo .

Viene creato un nuovo HKAnchoredObjectQuery oggetto per il punto dati desiderato (in questo caso HKQuantityTypeIdentifier.ActiveEnergyBurned per il punto dati Bruciato energia attiva), non viene imposto alcun limite alla quantità di dati restituiti (HKSampleQuery.NoLimit) e viene definito un gestore di aggiornamento per gestire i dati restituiti all'app da HealthKit.

Il gestore di aggiornamento verrà chiamato ogni volta che vengono recapitati nuovi dati all'app per il punto dati specificato. Se non viene restituito alcun errore, l'app può leggere in modo sicuro i dati, eseguire i calcoli necessari e aggiornare l'interfaccia utente in base alle esigenze.

Il codice esegue un ciclo su tutti gli esempi (HKSample) restituiti nella addedObjects matrice e li esegue il cast in un esempio quantity (HKQuantitySample). Ottiene quindi il doppio valore del campione come joule (HKUnit.Joule) e lo accumula nel totale in esecuzione dell'energia attiva bruciata per l'allenamento e aggiorna l'interfaccia utente.

Notifica obiettivo raggiunto

Come accennato in precedenza, quando l'utente raggiunge un obiettivo nell'app di allenamento (ad esempio il completamento del primo miglio di una corsa), può inviare feedback aptico all'utente tramite il motore Taptic. L'app dovrebbe anche aggiornarla a questo punto, perché l'utente genererà più probabilmente il polso per visualizzare l'evento che ha richiesto il feedback.

Per riprodurre il feedback aptico, usare il codice seguente:

// Play haptic feedback
WKInterfaceDevice.CurrentDevice.PlayHaptic (WKHapticType.Notification);

Osservazione degli eventi

Gli eventi sono timestamp che l'app può usare per evidenziare determinati punti durante l'allenamento dell'utente. Alcuni eventi verranno creati direttamente dall'app e salvati nell'allenamento e alcuni eventi verranno creati automaticamente da HealthKit.

Per osservare gli eventi creati da HealthKit, l'app eseguirà l'override del DidGenerateEvent metodo di 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 ha aggiunto i nuovi tipi di eventi seguenti in watchOS 3:

  • HKWorkoutEventType.Lap - Sono per eventi che interrompono l'allenamento in parti di distanza uguali. Ad esempio, per contrassegnare un giro intorno a un tracciato durante l'esecuzione.
  • HKWorkoutEventType.Marker - Sono per punti di interesse arbitrari all'interno dell'allenamento. Ad esempio, raggiungere un punto specifico sul percorso di una corsa all'aperto.

Questi nuovi tipi possono essere creati dall'app e archiviati nell'allenamento per usarli successivamente nella creazione di grafici e statistiche.

Per creare un evento marcatore, eseguire le operazioni seguenti:

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);
}

Questo codice crea una nuova istanza di un evento Marker (HKWorkoutEvent) e lo salva in una raccolta privata di eventi (che verrà successivamente scritto nella sessione di allenamento) e notifica all'utente dell'evento tramite aptici.

Sospensione e ripresa degli allenamenti

In qualsiasi momento in una sessione di allenamento, l'utente può sospendere temporaneamente l'allenamento e riprenderlo in un secondo momento. Ad esempio, potrebbero sospendere un'esecuzione interna per eseguire una chiamata importante e riprendere l'esecuzione dopo il completamento della chiamata.

L'interfaccia utente dell'app deve fornire un modo per sospendere e riprendere l'allenamento (chiamando healthKit) in modo che Apple Watch possa risparmiare energia e spazio dati mentre l'utente ha sospeso l'attività. Inoltre, l'app deve ignorare tutti i nuovi punti dati che potrebbero essere ricevuti quando la sessione di allenamento è in uno stato sospeso.

HealthKit risponderà alle chiamate in pausa e ripresa generando eventi pausa e ripresa. Mentre la sessione di allenamento è sospesa, nessun nuovo evento o dati verrà inviato all'app da HealthKit fino a quando la sessione non viene ripresa.

Usare il codice seguente per sospendere e riprendere una sessione di allenamento:

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);
}

Gli eventi Pause e Resume che verranno generati da HealthKit possono essere gestiti eseguendo l'override HKWorkoutSessionDelegatedel DidGenerateEvent metodo di :

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;
  }
}

Eventi di movimento

Inoltre, novità di watchOS 3, sono gli eventi Motion Paused (HKWorkoutEventType.MotionPaused) e Motion Resumed (HKWorkoutEventType.MotionResumed). Questi eventi vengono generati automaticamente da HealthKit durante un allenamento in esecuzione quando l'utente inizia e smette di muoversi.

Quando l'app riceve un evento Motion Paused, deve interrompere la raccolta dei dati fino a quando l'utente non riprende il movimento e l'evento Motion Resumes viene ricevuto. L'app non deve sospendere la sessione di allenamento in risposta a un evento Motion Paused.

Importante

Gli eventi Motion Paused e Motion Resume sono supportati solo per il tipo di attività RunningWorkout (HKWorkoutActivityType.Running).

Anche in questo caso, questi eventi possono essere gestiti eseguendo l'override HKWorkoutSessionDelegatedel DidGenerateEvent metodo di :

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;
  }
}

Fine e salvataggio della sessione di allenamento

Quando l'utente ha completato l'allenamento, l'app dovrà terminare la sessione di allenamento corrente e salvarla nel database HealthKit. Gli allenamenti salvati in HealthKit verranno visualizzati automaticamente nell'elenco attività di allenamento.

Novità di iOS 10, questo include anche l'elenco Elenco attività di allenamento nell'i Telefono dell'utente. Quindi, anche se l'Apple Watch non è nelle vicinanze, l'allenamento verrà presentato sul telefono.

Gli allenamenti che includono Energy Samples aggiorneranno l'anello di spostamento dell'utente nell'app Attività in modo che le app di terze parti possano ora contribuire agli obiettivi di spostamento giornalieri dell'utente.

I passaggi seguenti sono necessari per terminare e salvare una sessione di allenamento:

Fine e salvataggio del diagramma sessione di allenamento

  1. Prima di tutto, l'app dovrà terminare la sessione di allenamento.
  2. La sessione di allenamento viene salvata in HealthKit.
  3. Aggiungere eventuali campioni (ad esempio energia bruciata o distanza) alla sessione di allenamento salvata.

Fine della sessione

Per terminare la sessione di allenamento, chiamare il EndWorkoutSession metodo del HKHealthStore passaggio in HKWorkoutSession:

public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
...

public void EndOutdoorRun ()
{
  // End the current workout session
  HealthStore.EndWorkoutSession (WorkoutSession);
}

In questo modo i sensori dei dispositivi verranno reimpostati sulla modalità normale. Al termine dell'allenamento, HealthKit riceverà un callback al DidChangeToState metodo del 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;
  }

}

Salvataggio della sessione

Una volta terminata la sessione di allenamento, l'app dovrà creare un allenamento (HKWorkout) e salvarlo (insieme a un evento) nell'archivio dati 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
    }
  });

}

Questo codice crea la quantità totale di energia bruciata e distanza per l'allenamento come HKQuantity oggetti. Viene creato un dizionario di metadati che definiscono l'allenamento e viene specificata la posizione dell'allenamento:

metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));

Viene creato un nuovo HKWorkout oggetto con gli stessi HKWorkoutActivityType valori di HKWorkoutSession, le date di inizio e di fine, l'elenco di eventi (accumulati dalle sezioni precedenti), l'energia bruciata, la distanza totale e il dizionario dei metadati. Questo oggetto viene salvato nell'Archivio integrità ed eventuali errori gestiti.

Aggiunta di esempi

Quando l'app salva un set di campioni in un allenamento, HealthKit genera una connessione tra gli esempi e l'allenamento stesso in modo che l'app possa eseguire query su HealthKit in un secondo momento per tutti i campioni associati a un determinato allenamento. Usando queste informazioni, l'app può generare grafici dai dati di allenamento e tracciarli in base a una sequenza temporale di allenamento.

Per consentire a un'app di contribuire all'anello di movimento dell'app Activity, deve includere campioni di energia con l'allenamento salvato. Inoltre, i totali per distanza e energia devono corrispondere alla somma di tutti i campioni associati dall'app a un allenamento salvato.

Per aggiungere campioni a un allenamento salvato, eseguire le operazioni seguenti:

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
    }
  });
}

Facoltativamente, l'app può calcolare e creare un sottoinsieme più piccolo di campioni o un mega campione (che estende l'intera gamma dell'allenamento) che poi viene associato all'allenamento salvato.

Allenamenti e iOS 10

Ogni app di allenamento watchOS 3 ha un'app di allenamento basata su iOS 10 padre e, novità di iOS 10, questa app iOS può essere usata per avviare un allenamento che metterà Apple Watch in modalità allenamento (senza intervento dell'utente) ed eseguire l'app watchOS in modalità Esecuzione in background (vedi Informazioni sull'esecuzione in background sopra per altri dettagli).

Mentre l'app watchOS è in esecuzione, può usare Watch Connessione ivity per la messaggistica e la comunicazione con l'app iOS padre.

Esaminare il funzionamento di questo processo:

Diagramma delle comunicazioni i Telefono e Apple Watch

  1. L'app i Telefono crea un HKWorkoutConfiguration oggetto e imposta il tipo di allenamento e la posizione.
  2. L'oggetto HKWorkoutConfiguration viene inviato alla versione apple Watch dell'app e, se non è già in esecuzione, viene avviato dal sistema.
  3. Usando il passato in Configurazione allenamento, l'app watchOS 3 avvia una nuova sessione di allenamento (HKWorkoutSession).

Importante

Affinché l'app padre i Telefono avvii un allenamento in Apple Watch, l'app watchOS 3 deve avere Background Running abilitato. Per altri dettagli, vedere Abilitazione dell'esecuzione in background in precedenza.

Questo processo è molto simile al processo di avvio di una sessione di allenamento direttamente nell'app watchOS 3. In i Telefono usare il codice seguente:

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
        ...
      }
    });
  }
}

Questo codice garantisce che la versione watchOS dell'app sia installata e che la versione i Telefono possa connettersi per prima:

if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
  ...
}

Quindi crea un come HKWorkoutConfiguration di consueto e usa il StartWatchApp metodo di HKHealthStore per inviarlo ad Apple Watch e avviare l'app e la sessione di allenamento.

E nell'app del sistema operativo watch usare il codice seguente in 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);
}

Accetta HKWorkoutConfiguration e crea un nuovo HKWorkoutSession oggetto e associa un'istanza dell'oggetto personalizzato HKWorkoutSessionDelegate. La sessione di allenamento viene avviata contro l'HealthKit Health Store dell'utente.

Riunire tutti i pezzi

Prendendo tutte le informazioni presentate in questo documento, un'app di allenamento basata su watchOS 3 e la sua app di allenamento basata su iOS 10 padre potrebbero includere le parti seguenti:

  1. iOS 10 ViewController.cs : gestisce l'avvio di una sessione watch Connessione ivity e un allenamento su Apple Watch.
  2. watchOS 3 ExtensionDelegate.cs : gestisce la versione watchOS 3 dell'app di allenamento.
  3. watchOS 3 OutdoorRunDelegate.cs - Personalizzato HKWorkoutSessionDelegate per gestire gli eventi per l'allenamento.

Importante

Il codice illustrato nelle sezioni seguenti include solo le parti necessarie per implementare le nuove funzionalità avanzate fornite alle app Di allenamento in watchOS 3. Tutto il codice di supporto e il codice da presentare e aggiornare l'interfaccia utente non è incluso, ma può essere facilmente creato seguendo la nostra altra documentazione watchOS.

ViewController.cs

Il ViewController.cs file nella versione padre di iOS 10 dell'app di allenamento include il codice seguente:

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

Il ExtensionDelegate.cs file nella versione watchOS 3 dell'app di allenamento include il codice seguente:

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

Il OutdoorRunDelegate.cs file nella versione watchOS 3 dell'app di allenamento include il codice seguente:

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
  }
}

Consigli per iniziare

Apple suggerisce di usare le procedure consigliate seguenti durante la progettazione e l'implementazione di app di allenamento in watchOS 3 e iOS 10:

  • Assicurarsi che l'app watchOS 3 Workout sia ancora funzionante anche quando non è in grado di connettersi all'i Telefono e alla versione iOS 10 dell'app.
  • Usa la distanza healthKit quando IL GPS non è disponibile perché è in grado di generare campioni di distanza senza GPS.
  • Consentire all'utente di avviare l'allenamento da Apple Watch o i Telefono.
  • Consenti all'app di visualizzare gli allenamenti da altre origini (ad esempio altre app di terze parti) nelle visualizzazioni dei dati cronologici.
  • Assicurati che l'app non visualizzi gli allenamenti eliminati nei dati cronologici.

Riepilogo

Questo articolo ha illustrato i miglioramenti apportati da Apple alle app di allenamento in watchOS 3 e come implementarle in Xamarin.