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à.
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:
Nel Esplora soluzioni fare doppio clic sul file dell'app complementare i Telefono dell'app
Info.plist
watch per aprirlo per la modifica.Passare alla visualizzazione Origine:
Aggiungere una nuova chiave denominata
WKBackgroundModes
e impostare Type suArray
:Aggiungere un nuovo elemento alla matrice con il tipo di
String
e il valore :workout-processing
Salvare le modifiche apportate al file.
Avvio di una sessione di allenamento
Ci sono tre passaggi principali per avviare una sessione di allenamento:
- L'app deve richiedere l'autorizzazione per accedere ai dati in HealthKit.
- Creare un oggetto Configurazione allenamento per il tipo di allenamento avviato.
- 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:
In Esplora soluzioni fare doppio clic sul file
Entitlements.plist
per aprirlo e modificarlo.Scorrere fino alla fine e selezionare Abilita HealthKit:
Salvare le modifiche apportate al file.
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.
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:
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:
- Osservazione di esempi: l'app dovrà recuperare informazioni da HealthKit su cui verrà eseguita e visualizzata all'utente.
- 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).
- Immettere Stato di esecuzione: la sessione è stata avviata ed è attualmente in esecuzione.
- 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.
- 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 HKWorkoutSessionDelegate
del 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 HKWorkoutSessionDelegate
del 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:
- Prima di tutto, l'app dovrà terminare la sessione di allenamento.
- La sessione di allenamento viene salvata in HealthKit.
- 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:
- L'app i Telefono crea un
HKWorkoutConfiguration
oggetto e imposta il tipo di allenamento e la posizione. - L'oggetto
HKWorkoutConfiguration
viene inviato alla versione apple Watch dell'app e, se non è già in esecuzione, viene avviato dal sistema. - 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:
- iOS 10
ViewController.cs
: gestisce l'avvio di una sessione watch Connessione ivity e un allenamento su Apple Watch. - watchOS 3
ExtensionDelegate.cs
: gestisce la versione watchOS 3 dell'app di allenamento. - watchOS 3
OutdoorRunDelegate.cs
- PersonalizzatoHKWorkoutSessionDelegate
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.