Aplicaciones de entrenamiento de watchOS en Xamarin
Este artículo cubre las mejoras que Apple ha introducido en las aplicaciones de entrenamiento en watchOS 3 y cómo implementarlas en Xamarin.
Novedades de watchOS 3, las aplicaciones relacionadas con el entrenamiento tienen la capacidad de ejecutarse en segundo plano en Apple Watch y obtener acceso a los datos de HealthKit. Su aplicación principal basada en iOS 10 también tiene la capacidad de iniciar la aplicación basada en watchOS 3 sin intervención del usuario.
Los siguientes temas se tratarán en detalle:
Acerca de las aplicaciones de entrenamiento
Los usuarios de aplicaciones de fitness y entrenamiento pueden ser altamente dedicados, dedicando varias horas del día hacia sus objetivos de salud y fitness. Como resultado, esperan aplicaciones con capacidad de respuesta y fáciles de usar que recopilan y muestran datos con precisión e integran sin problemas con Apple Health.
Una aplicación de fitness o entrenamiento bien diseñada ayuda a los usuarios a trazar sus actividades para alcanzar sus objetivos de fitness. Al usar Apple Watch, las aplicaciones de fitness y entrenamiento tienen acceso instantáneo a la frecuencia cardíaca, la quemadura de calorías y la detección de actividad.
Novedad de watchOS 3, Background Running ofrece a las aplicaciones relacionadas con el entrenamiento la capacidad de ejecutarse en segundo plano en Apple Watch y obtener acceso a los datos de HealthKit.
En este documento se presenta la característica Ejecución en segundo plano, se explica el ciclo de vida de la aplicación de entrenamiento y se muestra cómo una aplicación de entrenamiento puede contribuir a los anillos de actividad del usuario en Apple Watch.
Acerca de las sesiones de entrenamiento
El corazón de cada aplicación de entrenamiento es una sesión de entrenamiento (HKWorkoutSession
) que el usuario puede iniciar y detener. La API de sesión de entrenamiento es fácil de implementar y proporciona varias ventajas para una aplicación de entrenamiento, como:
- Detección de movimiento y quema de calorías en función del tipo de actividad.
- Contribución automática a los anillos de actividad del usuario.
- Mientras se encuentra en una sesión, la aplicación se mostrará automáticamente cada vez que el usuario reactiva el dispositivo (ya sea levantando la muñeca o interactuando con Apple Watch).
Acerca de la ejecución en segundo plano
Como se indicó anteriormente, con watchOS 3, se puede establecer una aplicación de entrenamiento para ejecutarse en segundo plano. El uso de background Running una aplicación de entrenamiento puede procesar datos de los sensores de Apple Watch mientras se ejecuta en segundo plano. Por ejemplo, una aplicación puede seguir supervisando la frecuencia cardíaca del usuario, aunque ya no se muestre en pantalla.
La ejecución en segundo plano también proporciona la capacidad de presentar comentarios en directo al usuario en cualquier momento durante una sesión de entrenamiento activa, como enviar una alerta háptica para informar al usuario de su progreso actual.
Además, La ejecución en segundo plano permite a la aplicación actualizar rápidamente su interfaz de usuario para que el usuario tenga los datos más recientes cuando vea rápidamente su Apple Watch.
Para mantener un alto rendimiento en Apple Watch, una aplicación de reloj que usa Background Running debe limitar la cantidad de trabajo en segundo plano para conservar la batería. Si una aplicación usa una CPU excesiva en segundo plano, puede suspenderla watchOS.
Habilitación de la ejecución en segundo plano
Para habilitar Background Running, haga lo siguiente:
En el Explorador de soluciones, haga doble clic en el archivo de
Info.plist
la aplicación iPhone complementario de la extensión Watch para abrirlo para su edición.Cambie a la vista Código fuente:
Agregue una nueva clave denominada
WKBackgroundModes
y establezca el tipo enArray
:Agregue un nuevo elemento a la matriz con el tipo de
String
y un valor deworkout-processing
:Guarde los cambios en el archivo.
Iniciar una sesión de entrenamiento
Hay tres pasos principales para iniciar una sesión de entrenamiento:
- La aplicación debe solicitar autorización para acceder a los datos en HealthKit.
- Cree un objeto de configuración de entrenamiento para el tipo de entrenamiento que se está iniciando.
- Cree e inicie una sesión de entrenamiento con la configuración de entrenamiento recién creada.
Solicitud de autorización
Para que una aplicación pueda acceder a los datos de HealthKit del usuario, debe solicitar y recibir autorización del usuario. Dependiendo de la naturaleza de la aplicación de entrenamiento, puede realizar los siguientes tipos de solicitudes:
- Autorización para escribir datos:
- Entrenamientos
- Autorización para leer datos:
- Energía quemada
- Distancia
- Frecuencia cardíaca
Para que una aplicación pueda solicitar autorización, debe configurarse para acceder a HealthKit.
Haga lo siguiente:
Haga doble clic en el archivo
Entitlements.plist
en el Explorador de soluciones para abrirlo para su edición.Desplácese hasta la parte inferior y active Habilitar HealthKit:
Guarde los cambios en el archivo.
Siga las instrucciones de las secciones Id. de aplicación explícita y Perfil de aprovisionamiento y Asociación del id. de aplicación y perfil de aprovisionamiento con la aplicación de Xamarin.iOS del artículo Introducción a HealthKit para aprovisionar correctamente la aplicación.
Por último, use las instrucciones del Kit de mantenimiento de programación y solicitar permiso desde las secciones Usuario del artículo Introducción a HealthKit para solicitar autorización para acceder al almacén de datos HealthKit del usuario.
Establecer la configuración de entrenamiento
Las sesiones de entrenamiento se crean mediante un objeto de configuración de entrenamiento (HKWorkoutConfiguration
) que especifica el tipo de entrenamiento (como HKWorkoutActivityType.Running
) y la ubicación del entrenamiento (como HKWorkoutSessionLocationType.Outdoor
):
using HealthKit;
...
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
Crear un delegado de sesión de entrenamiento
Para controlar los eventos que pueden producirse durante una sesión de entrenamiento, la aplicación deberá crear una instancia de delegado de sesión de entrenamiento. Agregue una nueva clase al proyecto y básela fuera de la clase HKWorkoutSessionDelegate
. Para ver el ejemplo de una ejecución al aire libre, podría tener un aspecto similar al siguiente:
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
}
}
Esta clase crea varios eventos que se generarán como el estado de la sesión de entrenamiento cambia (DidChangeToState
) y si se produce un error en la sesión de entrenamiento (DidFail
).
Creación de una sesión de entrenamiento
Con la configuración de entrenamiento y el delegado de sesión de entrenamiento creado anteriormente para crear una nueva sesión de entrenamiento e iniciarla en el almacén de HealthKit predeterminado del usuario:
using HealthKit;
...
#region Computed Properties
public HKHealthStore HealthStore { get; set;} = new HKHealthStore ();
public OutdoorRunDelegate RunDelegate { get; set; }
#endregion
...
private void StartOutdoorRun ()
{
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
// Create workout session
// Start workout session
NSError error = null;
var workoutSession = new HKWorkoutSession (configuration, out error);
// Successful?
if (error != null) {
// Report error to user and return
return;
}
// Create workout session delegate and wire-up events
RunDelegate = new OutdoorRunDelegate (HealthStore, workoutSession);
RunDelegate.Failed += () => {
// Handle the session failing
};
RunDelegate.Paused += () => {
// Handle the session being paused
};
RunDelegate.Running += () => {
// Handle the session running
};
RunDelegate.Ended += () => {
// Handle the session ending
};
// Start session
HealthStore.StartWorkoutSession (workoutSession);
}
Si la aplicación inicia esta sesión de entrenamiento y el usuario vuelve a su cara de reloj, se mostrará un pequeño icono verde "hombre en ejecución" encima de la cara:
Si el usuario pulsa este icono, volverá a la aplicación.
Recopilación y control de datos
Una vez configurada e iniciada una sesión de entrenamiento, la aplicación deberá recopilar datos sobre la sesión (como la frecuencia cardíaca del usuario) y controlar el estado de la sesión:
- Observación de ejemplos: la aplicación tendrá que recuperar información de HealthKit que se actuará y se mostrará al usuario.
- Observar eventos: la aplicación tendrá que responder a eventos generados por HealthKit o desde la interfaz de usuario de la aplicación (como el usuario que pausa el entrenamiento).
- Escriba Estado de ejecución: la sesión se ha iniciado y se está ejecutando actualmente.
- Entrar estado en pausa: el usuario ha pausado la sesión de entrenamiento actual y puede reiniciarla en una fecha posterior. El usuario puede cambiar entre los estados en ejecución y en pausa varias veces en una sola sesión de entrenamiento.
- Finalizar sesión de entrenamiento: en cualquier momento el usuario puede finalizar la sesión de entrenamiento o puede expirar y terminar por sí mismo si era un entrenamiento medido (por ejemplo, una carrera de dos millas).
El último paso es guardar los resultados de la sesión de entrenamiento en el almacén de datos HealthKit del usuario.
Observar ejemplos de HealthKit
La aplicación deberá abrir un delimitador de consulta de objetos para cada uno de los puntos de datos de HealthKit que le interesen, como la frecuencia cardíaca o la energía activa quemada. Para cada punto de datos que se observa, es necesario crear un controlador de actualización para capturar nuevos datos a medida que se envía a la aplicación.
A partir de estos puntos de datos, la aplicación puede acumular totales (como la distancia de ejecución total) y actualizarlo según sea necesario. Además, la aplicación puede notificar a los usuarios cuando hayan alcanzado un objetivo o logro específico, como completar la siguiente milla de una carrera.
Eche un vistazo al código de ejemplo siguiente:
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 predicado para establecer la fecha de inicio que quiere obtener datos para usar el GetPredicateForSamples
método. Crea un conjunto de dispositivos para extraer información de HealthKit mediante el método GetPredicateForObjectsFromDevices
, en este caso solo Apple Watch local (HKDevice.LocalDevice
). Los dos predicados se combinan en un predicado compuesto (NSCompoundPredicate
) mediante el método CreateAndPredicate
.
Se crea un nuevo HKAnchoredObjectQuery
para el punto de datos deseado (en este caso HKQuantityTypeIdentifier.ActiveEnergyBurned
para el punto de datos quemado de energía activa), no se impone ningún límite en la cantidad de datos devueltos (HKSampleQuery.NoLimit
) y se define un controlador de actualización para controlar los datos que se devuelven a la aplicación desde HealthKit.
Se llamará al controlador de actualizaciones cada vez que se entreguen nuevos datos a la aplicación para el punto de datos especificado. Si no se devuelve ningún error, la aplicación puede leer los datos de forma segura, realizar los cálculos necesarios y actualizar su interfaz de usuario según sea necesario.
El código recorre en bucle todos los ejemplos (HKSample
) devueltos en la addedObjects
matriz y los convierte en un ejemplo de cantidad (HKQuantitySample
). A continuación, obtiene el doble valor de la muestra como un joule (HKUnit.Joule
) y lo acumula en el total de energía activa quemada para el entrenamiento y actualiza la interfaz de usuario.
Notificación de objetivos alcanzados
Como se mencionó anteriormente, cuando el usuario logra un objetivo en la aplicación de entrenamiento (como completar la primera milla de una carrera), puede enviar comentarios hápticos al usuario a través del YTaptic Engine. La aplicación también debe actualizar la interfaz de usuario en este momento, ya que es más probable que el usuario genere su muñeca para ver el evento que ha generado los comentarios.
Para reproducir los comentarios hápticos, use el código siguiente:
// Play haptic feedback
WKInterfaceDevice.CurrentDevice.PlayHaptic (WKHapticType.Notification);
Observación de eventos
Los eventos son marcas de tiempo que la aplicación puede usar para resaltar determinados puntos durante el entrenamiento del usuario. La aplicación creará algunos eventos directamente y se guardará en el entrenamiento y HealthKit creará automáticamente algunos eventos.
Para observar los eventos creados por HealthKit, la aplicación invalidará el DidGenerateEvent
método de 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 agregado los siguientes tipos de eventos nuevos en watchOS 3:
HKWorkoutEventType.Lap
- Son para eventos que rompen el entrenamiento en partes de la misma distancia. Por ejemplo, para marcar una vuelta alrededor de una pista mientras se ejecuta.HKWorkoutEventType.Marker
- Son para puntos arbitrarios de interés dentro del entrenamiento. Por ejemplo, alcanzar un punto específico en la ruta de una carrera al aire libre.
La aplicación puede crear estos nuevos tipos y almacenarlos en el entrenamiento para usarlos posteriormente en la creación de gráficos y estadísticas.
Para crear un evento de marcador, haga lo siguiente:
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);
}
Este código crea una nueva instancia de un evento de marcador (HKWorkoutEvent
) y la guarda en una colección privada de eventos (que posteriormente se escribirá en la sesión de entrenamiento) y notifica al usuario del evento a través de hápticos.
Pausar y reanudar entrenamientos
En cualquier momento de una sesión de entrenamiento, el usuario puede pausar temporalmente el entrenamiento y reanudarlo más adelante. Por ejemplo, podrían pausar una ejecución interior para tomar una llamada importante y reanudar la ejecución una vez completada la llamada.
La interfaz de usuario de la aplicación debe proporcionar una manera de pausar y reanudar el entrenamiento (llamando a HealthKit) para que Apple Watch pueda conservar el espacio de energía y datos mientras el usuario ha suspendido su actividad. Además, la aplicación debe omitir los nuevos puntos de datos que se pueden recibir cuando la sesión de entrenamiento está en un estado en pausa.
HealthKit responderá a las llamadas de pausa y reanudación mediante la generación de eventos Pause y Resume. Mientras se pausa la sesión de entrenamiento, HealthKit no enviará ningún evento o datos nuevos a la aplicación hasta que se reanude la sesión.
Use el código siguiente para pausar y reanudar una sesión de entrenamiento:
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);
}
Los eventos Pause y Resume que se generarán desde HealthKit se pueden controlar invalidando el DidGenerateEvent
método de 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;
}
}
Eventos de movimiento
También es nuevo en watchOS 3, son los eventos Motion Paused (HKWorkoutEventType.MotionPaused
) y Motion Resumed (HKWorkoutEventType.MotionResumed
). HealthKit genera automáticamente estos eventos durante un entrenamiento en ejecución cuando el usuario comienza y deja de moverse.
Cuando la aplicación recibe un evento Motion Paused, debe dejar de recopilar datos hasta que el usuario reanude el movimiento y se reciba el evento Motion Resumes. La aplicación no debe pausar la sesión de entrenamiento en respuesta a un evento Motion Paused.
Importante
Los eventos Motion Paused y Motion Resume solo se admiten para el tipo de actividad RunningWorkout (HKWorkoutActivityType.Running
).
De nuevo, estos eventos se pueden controlar invalidando el DidGenerateEvent
método de 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;
}
}
Finalizar y guardar la sesión de entrenamiento
Cuando el usuario haya completado su entrenamiento, la aplicación tendrá que finalizar la sesión de entrenamiento actual y guardarla en la base de datos HealthKit. Los entrenamientos guardados en HealthKit se mostrarán automáticamente en la lista de actividades de entrenamiento.
Novedad de iOS 10, esto incluye también la lista Lista de actividades de entrenamiento en el iPhone del usuario. Así que incluso si el Apple Watch no está cerca, el entrenamiento se presentará en el teléfono.
Los entrenamientos que incluyen muestras de energía actualizarán el anillo de movimiento del usuario en la aplicación Actividades, por lo que las aplicaciones de terceros ahora pueden contribuir a los objetivos de movimiento diario del usuario.
Los pasos siguientes son necesarios para finalizar y guardar una sesión de entrenamiento:
- En primer lugar, la aplicación tendrá que finalizar la sesión de entrenamiento.
- La sesión de entrenamiento se guarda en HealthKit.
- Agregue cualquier muestra (como la energía quemada o la distancia) a la sesión de entrenamiento guardada.
Finalizar la sesión
Para finalizar la sesión de entrenamiento, llame al EndWorkoutSession
método del HKHealthStore
paso en HKWorkoutSession
:
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
...
public void EndOutdoorRun ()
{
// End the current workout session
HealthStore.EndWorkoutSession (WorkoutSession);
}
Esto restablecerá los sensores de los dispositivos a su modo normal. Cuando HealthKit termine de finalizar el entrenamiento, recibirá una devolución de llamada al DidChangeToState
método de 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;
}
}
Guardar la sesión
Una vez que la aplicación haya finalizado la sesión de entrenamiento, deberá crear un entrenamiento () y guardarlo (HKWorkout
junto con un evento) en el almacén de datos de 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
}
});
}
Este código crea la cantidad total de energía quemada y distancia necesaria para el entrenamiento como HKQuantity
objetos. Se crea un diccionario de metadatos que definen el entrenamiento y se especifica la ubicación del entrenamiento:
metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));
Se crea un nuevo HKWorkout
objeto con el mismo HKWorkoutActivityType
que HKWorkoutSession
,las fechas de inicio y finalización, la lista de eventos (que se acumulan en las secciones anteriores), la energía quemada, la distancia total y el diccionario de metadatos. Este objeto se guarda en el Almacén de estado y se controlan los errores.
Agregar ejemplos
Cuando la aplicación guarda un conjunto de muestras en un entrenamiento, HealthKit genera una conexión entre los ejemplos y el propio entrenamiento para que la aplicación pueda consultar HealthKit en una fecha posterior para todas las muestras asociadas a un entrenamiento determinado. Con esta información, la aplicación puede generar gráficos a partir de los datos de entrenamiento y trazarlos con una escala de tiempo de entrenamiento.
Para que una aplicación contribuya al anillo de movimiento de la aplicación de actividad, debe incluir muestras de energía con el entrenamiento guardado. Además, los totales de distancia y energía deben coincidir con la suma de las muestras que la aplicación asocia con un entrenamiento guardado.
Para agregar muestras a un entrenamiento guardado, haga lo siguiente:
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
}
});
}
Opcionalmente, la aplicación puede calcular y crear un subconjunto más pequeño de muestras o una mega muestra (que abarca todo el rango del entrenamiento) que luego se asocia con el entrenamiento guardado.
Entrenamientos e iOS 10
Cada aplicación de entrenamiento watchOS 3 tiene una aplicación de entrenamiento basada en iOS 10 principal y, nueva en iOS 10, esta aplicación de iOS se puede usar para iniciar un entrenamiento que colocará Apple Watch en el modo de entrenamiento (sin intervención del usuario) y ejecutar la aplicación watchOS en el modo de ejecución en segundo plano (vea Acerca de la ejecución en segundo plano arriba para obtener más detalles).
Mientras se ejecuta la aplicación watchOS, puede usar WatchConnectivity para la mensajería y la comunicación con la aplicación principal de iOS.
Eche un vistazo a cómo funciona este proceso:
- La aplicación de iPhone crea un
HKWorkoutConfiguration
objeto y establece el tipo de entrenamiento y la ubicación. - El
HKWorkoutConfiguration
objeto se envía a la versión de Apple Watch de la aplicación y, si aún no se está ejecutando, el sistema lo inicia. - Con la configuración de entrenamiento pasada, la aplicación watchOS 3 inicia una nueva sesión de entrenamiento (
HKWorkoutSession
).
Importante
Para que la aplicación principal de iPhone inicie un entrenamiento en Apple Watch, la aplicación watchOS 3 debe tener habilitada la ejecución en segundo plano. Consulte Habilitación de la ejecución en segundo plano anterior para obtener más información.
Este proceso es muy similar al proceso de iniciar una sesión de entrenamiento en la aplicación watchOS 3 directamente. En el iPhone, use el código siguiente:
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
...
}
});
}
}
Este código garantiza que la versión watchOS de la aplicación esté instalada y que la versión de iPhone pueda conectarse primero a ella:
if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
...
}
A continuación, crea un HKWorkoutConfiguration
como de costumbre y usa el StartWatchApp
método de HKHealthStore
para enviarlo a Apple Watch e iniciar la aplicación y la sesión de entrenamiento.
Y en la aplicación watch OS, use el código siguiente en 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);
}
Toma HKWorkoutConfiguration
y crea una nueva HKWorkoutSession
instancia de y adjunta una instancia de custom HKWorkoutSessionDelegate
. La sesión de entrenamiento se inicia en el HealthKit Health Store del usuario.
Reunir todas las piezas
Tomando toda la información presentada en este documento, una aplicación de entrenamiento basada en watchOS 3 y su aplicación de entrenamiento basada en iOS 10 principal podría incluir las siguientes partes:
- iOS 10
ViewController.cs
: controla el inicio de una sesión de conectividad de inspección y un entrenamiento en Apple Watch. - watchOS 3
ExtensionDelegate.cs
: controla la versión watchOS 3 de la aplicación de entrenamiento. - watchOS 3
OutdoorRunDelegate.cs
: un personalizadoHKWorkoutSessionDelegate
para controlar eventos para el entrenamiento.
Importante
El código que se muestra en las secciones siguientes solo incluye las partes necesarias para implementar las nuevas características mejoradas proporcionadas a las aplicaciones de entrenamiento en watchOS 3. No se incluye todo el código auxiliar y el código para presentar y actualizar la interfaz de usuario, pero se puede crear fácilmente siguiendo nuestra otra documentación de watchOS.
ViewController.cs
El ViewController.cs
archivo de la versión principal de iOS 10 de la aplicación de entrenamiento incluiría el código siguiente:
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
El ExtensionDelegate.cs
archivo de la versión watchOS 3 de la aplicación de entrenamiento incluiría el código siguiente:
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
El OutdoorRunDelegate.cs
archivo de la versión watchOS 3 de la aplicación de entrenamiento incluiría el código siguiente:
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
}
}
Procedimientos recomendados
Apple sugiere usar los siguientes procedimientos recomendados al diseñar e implementar aplicaciones de entrenamiento en watchOS 3 e iOS 10:
- Asegúrese de que la aplicación watchOS 3 Entrenamiento sigue funcionando incluso cuando no se puede conectar al iPhone y a la versión de iOS 10 de la aplicación.
- Usa la distancia de HealthKit cuando el GPS no está disponible, ya que es capaz de generar muestras de distancia sin GPS.
- Permitir al usuario iniciar el entrenamiento desde Apple Watch o el iPhone.
- Permitir que la aplicación muestre entrenamientos de otros orígenes (como otras aplicaciones de terceros) en sus vistas de datos históricas.
- Asegúrese de que la aplicación no muestre los entrenamientos eliminados en los datos históricos.
Resumen
Este artículo ha cubierto las mejoras que Apple ha introducido en las aplicaciones de entrenamiento en watchOS 3 y cómo implementarlas en Xamarin.