Aplicativos de treino watchOS em Xamarin
Este artigo aborda as melhorias que a Apple fez para treinar aplicativos no watchOS 3 e como implementá-los no Xamarin.
Novidade no watchOS 3, os aplicativos relacionados a exercícios têm a capacidade de rodar em segundo plano no Apple Watch e ter acesso aos dados do HealthKit. Seu aplicativo pai baseado no iOS 10 também tem a capacidade de iniciar o aplicativo baseado no watchOS 3 sem intervenção do usuário.
Os tópicos a seguir serão abordados em detalhes:
Sobre aplicativos de treino
Os usuários de aplicativos de fitness e treino podem ser altamente dedicados, dedicando várias horas do dia para seus objetivos de saúde e fitness. Como resultado, eles esperam aplicativos responsivos e fáceis de usar que coletam e exibem dados com precisão e se integram perfeitamente ao Apple Health.
Um aplicativo de fitness ou treino bem projetado ajuda os usuários a traçar suas atividades para atingir suas metas de condicionamento físico. Ao usar o Apple Watch, os aplicativos de fitness e exercícios têm acesso instantâneo à frequência cardíaca, queima de calorias e detecção de atividade.
Novo no watchOS 3, o Background Running dá aos aplicativos relacionados ao treino a capacidade de rodar em segundo plano no Apple Watch e obter acesso aos dados do HealthKit.
Este documento apresentará o recurso Corrida em segundo plano, abordará o ciclo de vida do aplicativo de treino e mostrará como um aplicativo de treino pode contribuir para os Anéis de Atividade do usuário no Apple Watch.
Sobre as sessões de treino
O coração de cada aplicativo de treino é uma Sessão de Treino (HKWorkoutSession
) que o usuário pode iniciar e parar. A API Workout Session é fácil de implementar e fornece vários benefícios para um aplicativo de treino, como:
- Detecção de queima de movimento e calorias com base no Tipo de Atividade.
- Contribuição automática para os Anéis de Atividade do usuário.
- Durante uma sessão, o aplicativo será exibido automaticamente sempre que o usuário acordar o dispositivo (seja levantando o pulso ou interagindo com o Apple Watch).
Sobre a execução em segundo plano
Como dito acima, com o watchOS 3 um aplicativo de treino pode ser configurado para ser executado em segundo plano. Usando o plano de fundo Executar um aplicativo de treino pode processar dados dos sensores do Apple Watch enquanto é executado em segundo plano. Por exemplo, um aplicativo pode continuar monitorando a frequência cardíaca do usuário, mesmo que ele não seja mais exibido na tela.
A Corrida em Segundo Plano também fornece a capacidade de apresentar feedback ao vivo para o usuário a qualquer momento durante uma Sessão de Treino ativa, como enviar um alerta háptico para informar o usuário sobre seu progresso atual.
Além disso, a Execução em Segundo Plano permite que o aplicativo atualize rapidamente sua Interface do Usuário para que o usuário tenha os dados mais recentes quando olhar rapidamente para o Apple Watch.
Para manter o alto desempenho no Apple Watch, um aplicativo de relógio que usa a Execução em Segundo Plano deve limitar a quantidade de trabalho em segundo plano para economizar bateria. Se um aplicativo estiver usando CPU excessiva em segundo plano, ele poderá ser suspenso pelo watchOS.
Habilitando a execução em segundo plano
Para habilitar a Execução em Segundo Plano, faça o seguinte:
No Gerenciador de Soluções, clique duas vezes no arquivo do
Info.plist
aplicativo iPhone complementar da Extensão de Observação para abri-lo para edição.Alterne para a guia Código-fonte:
Adicione uma nova chave chamada
WKBackgroundModes
e defina o Tipo comoArray
:Adicione um novo item à matriz com o Tipo de
String
e um valor deworkout-processing
:Salve as alterações no arquivo.
Iniciando uma sessão de treino
Existem três passos principais para iniciar uma sessão de treino:
- O aplicativo deve solicitar autorização para acessar dados no HealthKit.
- Crie um objeto Configuração de Treino para o tipo de treino que está sendo iniciado.
- Crie e inicie uma Sessão de Treino usando a Configuração de Treino recém-criada.
Solicitando Autorização
Antes que um aplicativo possa acessar os dados do HealthKit do usuário, ele deve solicitar e receber autorização do usuário. Dependendo da natureza do aplicativo de treino, ele pode fazer os seguintes tipos de solicitações:
- Autorização para gravar dados:
- Exercícios
- Autorização para leitura de dados:
- Energia queimada
- Distância
- Frequência Cardíaca
Antes que um aplicativo possa solicitar autorização, ele precisa ser configurado para acessar o HealthKit.
Faça o seguinte:
No Gerenciador de Soluções, clique duas vezes no arquivo
Entitlements.plist
para abri-lo para edição.Role até a parte inferior e marque Ativar HealthKit:
Salve as alterações no arquivo.
Siga as instruções nas seções ID de aplicativo explícito e perfil de provisionamento e Associando a ID do aplicativo e o perfil de provisionamento ao seu aplicativo Xamarin.iOS do artigo Introdução ao HealthKit para provisionar corretamente o aplicativo.
Finalmente, use as instruções nas seções Programming Health Kit e Requesting Permission From the User do artigo Introduction to HealthKit para solicitar autorização para acessar o armazenamento de dados HealthKit do usuário.
Definindo a configuração do treino
As Sessões de Treino são criadas usando um objeto Configuração de Treino (HKWorkoutConfiguration
) que especifica o tipo de treino (como HKWorkoutActivityType.Running
) e o local de treino (como HKWorkoutSessionLocationType.Outdoor
):
using HealthKit;
...
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
Criando um delegado de sessão de treino
Para manipular os eventos que podem ocorrer durante uma Sessão de Treino, o aplicativo precisará criar uma instância de Delegado de Sessão de Treino. Adicione uma nova classe ao projeto e baseie-a fora da HKWorkoutSessionDelegate
classe. Para o exemplo de uma corrida ao ar livre, pode ser semelhante ao seguinte:
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
}
}
Essa classe cria vários eventos que serão gerados à medida que o estado da Sessão de Treino for alterado (DidChangeToState
) e se a Sessão de Treino falhar (DidFail
).
Criando uma sessão de treino
Usando a Configuração de Treino e o Delegado de Sessão de Treino criados acima para criar uma nova Sessão de Treino e iniciá-la na loja padrão do HealthKit do usuário:
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 o aplicativo iniciar esta sessão de treino e o usuário voltar para o mostrador do relógio, um pequeno ícone verde de "homem correndo" será exibido acima do rosto:
Se o usuário tocar nesse ícone, ele será levado de volta ao aplicativo.
Coleta e Controle de Dados
Depois que uma Sessão de Treino for configurada e iniciada, o aplicativo precisará coletar dados sobre a sessão (como a frequência cardíaca do usuário) e controlar o estado da sessão:
- Observando amostras - O aplicativo precisará recuperar informações do HealthKit que serão acionadas e exibidas ao usuário.
- Observando eventos - O aplicativo precisará responder a eventos gerados pelo HealthKit ou pela interface do usuário do aplicativo (como o usuário pausando o treino).
- Enter Running State - A sessão foi iniciada e está em execução no momento.
- Inserir estado pausado - O usuário pausou a sessão de treino atual e pode reiniciá-la posteriormente. O usuário pode alternar entre os estados de corrida e pausa várias vezes em uma única sessão de treino.
- Terminar Sessão de Treino - A qualquer momento, o usuário pode terminar a Sessão de Treino ou ela pode expirar e terminar por conta própria se foi um treino medido (como uma corrida de duas milhas).
A etapa final é salvar os resultados da Sessão de Treino no armazenamento de dados HealthKit do usuário.
Observando amostras do HealthKit
O aplicativo precisará abrir uma Consulta de Objeto Âncora para cada um dos pontos de dados do HealthKit nos quais está interessado, como frequência cardíaca ou energia ativa queimada. Para cada ponto de dados observado, um manipulador de atualização precisará ser criado para capturar novos dados à medida que são enviados ao aplicativo.
A partir desses pontos de dados, o aplicativo pode acumular totais (como a distância total de execução) e atualizar sua Interface do Usuário conforme necessário. Além disso, o aplicativo pode notificar os usuários quando eles atingirem uma meta ou conquista específica, como completar a próxima milha de uma corrida.
Dê uma olhada no seguinte código de exemplo:
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);
}
Ele cria um predicado para definir a data de início que deseja obter dados para usar o GetPredicateForSamples
método. Ele cria um conjunto de dispositivos para extrair informações do HealthKit usando o GetPredicateForObjectsFromDevices
método, neste caso apenas o Apple Watch local (HKDevice.LocalDevice
). Os dois predicados são combinados em um Predicado Composto (NSCompoundPredicate
) usando o CreateAndPredicate
método.
Um novo HKAnchoredObjectQuery
é criado para o ponto de dados desejado (neste caso HKQuantityTypeIdentifier.ActiveEnergyBurned
, para o ponto de dados Active Energy Burned), nenhum limite é imposto sobre a quantidade de dados retornados (HKSampleQuery.NoLimit
) e um manipulador de atualização é definido para lidar com os dados que estão sendo retornados ao aplicativo a partir do HealthKit.
O manipulador de atualização será chamado sempre que novos dados forem entregues ao aplicativo para o ponto de dados especificado. Se nenhum erro for retornado, o aplicativo poderá ler os dados com segurança, fazer os cálculos necessários e atualizar sua interface do usuário conforme necessário.
O código faz um loop sobre todas as amostras (HKSample
) retornadas na matriz e as converte em addedObjects
uma Amostra de Quantidade (HKQuantitySample
). Em seguida, ele obtém o valor duplo da amostra como um joule (HKUnit.Joule
) e o acumula no total de energia ativa queimada para o treino e atualiza a Interface do Usuário.
Notificação de Meta Atingida
Como mencionado acima, quando o usuário atinge um objetivo no aplicativo de treino (como completar a primeira milha de uma corrida), ele pode enviar feedback háptico para o usuário através do Taptic Engine. O aplicativo também deve atualizar sua interface do usuário neste momento, já que o usuário provavelmente levantará o pulso para ver o evento que motivou o feedback.
Para reproduzir o feedback háptico, use o seguinte código:
// Play haptic feedback
WKInterfaceDevice.CurrentDevice.PlayHaptic (WKHapticType.Notification);
Observando Eventos
Eventos são carimbos de data/hora que o aplicativo pode usar para destacar determinados pontos durante o treino do usuário. Alguns eventos serão criados diretamente pelo aplicativo e salvos no treino e alguns eventos serão criados automaticamente pelo HealthKit.
Para observar eventos criados pelo HealthKit, o aplicativo substituirá o DidGenerateEvent
método do 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;
}
}
A Apple adicionou os seguintes novos tipos de eventos no watchOS 3:
HKWorkoutEventType.Lap
- São para eventos que dividem o treino em porções de distância iguais. Por exemplo, para marcar uma volta ao redor de uma pista durante a corrida.HKWorkoutEventType.Marker
- São para pontos de interesse arbitrários dentro do treino. Por exemplo, chegar a um ponto específico na rota de uma corrida ao ar livre.
Esses novos tipos podem ser criados pelo aplicativo e armazenados no treino para uso posterior na criação de gráficos e estatísticas.
Para criar um Evento de Marcador, faça o seguinte:
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);
}
Esse código cria uma nova instância de um Evento de Marcador (HKWorkoutEvent
) e o salva em uma coleção privada de eventos (que posteriormente será gravada na Sessão de Treino) e notifica o usuário sobre o evento por meio de hápticas.
Pausando e retomando os treinos
A qualquer momento de uma sessão de treino, o usuário pode pausar temporariamente o treino e retomá-lo posteriormente. Por exemplo, eles podem pausar uma corrida interna para atender a uma chamada importante e retomar a execução após a conclusão da chamada.
A interface do usuário do aplicativo deve fornecer uma maneira de pausar e retomar o treino (chamando o HealthKit) para que o Apple Watch possa economizar energia e espaço de dados enquanto o usuário suspende sua atividade. Além disso, o aplicativo deve ignorar quaisquer novos pontos de dados que possam ser recebidos quando a Sessão de Treino estiver em um estado pausado.
O HealthKit responderá a pausar e retomar chamadas gerando eventos Pause e Resume. Enquanto a Sessão de Treino estiver pausada, nenhum novo evento ou dado será enviado ao aplicativo pelo HealthKit até que a sessão seja retomada.
Use o código a seguir para pausar e retomar uma sessão de treino:
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);
}
Os eventos Pause e Resume que serão gerados a partir do HealthKit podem ser manipulados substituindo o DidGenerateEvent
método do 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 Movimento
Outra novidade do watchOS 3 são os eventos Motion Paused (HKWorkoutEventType.MotionPaused
) e Motion Resumed (HKWorkoutEventType.MotionResumed
). Esses eventos são gerados automaticamente pelo HealthKit durante um treino de corrida quando o usuário inicia e para de se mover.
Quando o aplicativo recebe um evento Motion Paused, ele deve parar de coletar dados até que o usuário retome o movimento e o evento Motion Resumes seja recebido. O aplicativo não deve pausar a sessão de Treino em resposta a um evento de Movimento em Pausa.
Importante
Os eventos Motion Paused e Motion Resume são suportados apenas para o Tipo de Atividade RunningWorkout (HKWorkoutActivityType.Running
).
Novamente, esses eventos podem ser manipulados substituindo o DidGenerateEvent
método do 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;
}
}
Terminando e salvando a sessão de treino
Quando o usuário tiver concluído seu treino, o aplicativo precisará encerrar a Sessão de Treino atual e salvá-la no banco de dados HealthKit. Os treinos salvos no HealthKit serão exibidos automaticamente na Lista de Atividades de Treino.
Novo no iOS 10, isso inclui a lista de atividades de treino no iPhone do usuário também. Assim, mesmo que o Apple Watch não esteja por perto, o treino será apresentado no telefone.
Os treinos que incluem Amostras de Energia atualizarão o Anel de Movimentação do usuário no aplicativo Atividades para que os aplicativos de terceiros possam agora contribuir para as metas diárias de Movimentação do usuário.
As seguintes etapas são necessárias para encerrar e salvar uma sessão de treino:
- Primeiro, o aplicativo precisará encerrar a Sessão de Treino.
- A Sessão de Treino é salva no HealthKit.
- Adicione amostras (como energia queimada ou distância) à Sessão de Treino salva.
Encerrando a sessão
Para finalizar a Sessão de Treino, chame o EndWorkoutSession
método da HKHealthStore
passagem no HKWorkoutSession
:
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
...
public void EndOutdoorRun ()
{
// End the current workout session
HealthStore.EndWorkoutSession (WorkoutSession);
}
Isso redefinirá os sensores dos dispositivos para seu modo normal. Quando o HealthKit terminar de terminar o treino, ele receberá um retorno de chamada para o DidChangeToState
método do 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;
}
}
Salvando a sessão
Depois que o aplicativo terminar a Sessão de Treino, ele precisará criar um Treino (HKWorkout
) e salvá-lo (junto com um evento) no armazenamento de dados do 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 cria a quantidade total necessária de energia queimada e distância para o treino como HKQuantity
objetos. Um dicionário de metadados que definem o treino é criado e o local do treino é especificado:
metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));
Um novo HKWorkout
objeto é criado com o mesmo HKWorkoutActivityType
que o HKWorkoutSession
, as datas de início e fim, a lista de eventos (sendo acumulada das seções acima), a energia queimada, distância total e dicionário de metadados. Esse objeto é salvo no Repositório de Integridade e quaisquer erros são manipulados.
Adicionando amostras
Quando o aplicativo salva um conjunto de amostras em um Treino, o HealthKit gera uma conexão entre as amostras e o próprio Treino para que o aplicativo possa consultar o HealthKit posteriormente para todas as amostras associadas a um determinado treino. Usando essas informações, o aplicativo pode gerar gráficos a partir dos dados do treino e plotá-los em uma linha do tempo do treino.
Para que um aplicativo contribua para o Move Ring do aplicativo Atividade, ele deve incluir amostras de energia com o treino salvo. Além disso, os totais de distância e energia devem corresponder à soma de todas as amostras que o aplicativo associa a um treino salvo.
Para adicionar amostras a um treino salvo, faça o seguinte:
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, o aplicativo pode calcular e criar um subconjunto menor de amostras ou uma mega amostra (abrangendo toda a faixa do treino) que então é associada ao treino salvo.
Treinos e iOS 10
Cada aplicativo de treino do watchOS 3 tem um aplicativo de treino baseado no iOS 10 e, novidade no iOS 10, este aplicativo do iOS pode ser usado para iniciar um treino que colocará o Apple Watch no Modo de Treino (sem intervenção do usuário) e executará o aplicativo watchOS no modo de Execução em Segundo Plano (consulte Sobre a Execução em Segundo Plano acima para obter mais detalhes).
Enquanto o aplicativo watchOS está em execução, ele pode usar o WatchConnectivity para mensagens e comunicação com o aplicativo iOS pai.
Veja como funciona esse processo:
- O aplicativo para iPhone cria um
HKWorkoutConfiguration
objeto e define o Tipo e o Local do Treino. - O
HKWorkoutConfiguration
objeto é enviado a versão Apple Watch do app e, caso ainda não esteja em execução, é iniciado pelo sistema. - Usando a aprovação na Configuração de Treino, o aplicativo watchOS 3 inicia uma nova Sessão de Treino (
HKWorkoutSession
).
Importante
Para que o aplicativo pai do iPhone inicie um treino no Apple Watch, o aplicativo watchOS 3 deve ter a Execução em segundo plano ativada. Consulte Ativando a execução em segundo plano acima para obter mais detalhes.
Este processo é muito semelhante ao processo de iniciar uma sessão de treino no aplicativo watchOS 3 diretamente. No iPhone, use o seguinte código:
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 garante que a versão watchOS do aplicativo esteja instalada e a versão do iPhone possa se conectar a ele primeiro:
if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
...
}
Em seguida, ele cria um HKWorkoutConfiguration
como de costume e usa o HKHealthStore
StartWatchApp
método do para enviá-lo para o Apple Watch e iniciar o aplicativo e a sessão de treino.
E no aplicativo Watch OS, use o seguinte código no 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);
}
Ele pega o HKWorkoutConfiguration
e cria um novo HKWorkoutSession
e anexa uma instância do personalizado HKWorkoutSessionDelegate
. A Sessão de Treino é iniciada no HealthKit Health Store do usuário.
Juntando todas as peças
Tomando todas as informações apresentadas neste documento, um aplicativo de treino baseado no watchOS 3 e seu aplicativo de treino baseado no iOS 10 pai podem incluir as seguintes partes:
- iOS 10
ViewController.cs
- Inicia uma sessão de Conectividade do Watch e um treino no Apple Watch. - watchOS 3
ExtensionDelegate.cs
- Manipula a versão watchOS 3 do aplicativo de treino. - watchOS 3
OutdoorRunDelegate.cs
- Um personalizadoHKWorkoutSessionDelegate
para lidar com eventos para o treino.
Importante
O código mostrado nas seções a seguir inclui apenas as partes necessárias para implementar os novos recursos aprimorados fornecidos aos aplicativos de treino no watchOS 3. Todo o código de suporte e o código para apresentar e atualizar a interface do usuário não estão incluídos, mas podem ser facilmente criados seguindo nossa outra documentação do watchOS.
ViewController.cs
O ViewController.cs
arquivo na versão pai do iOS 10 do aplicativo de treino incluiria o seguinte código:
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
O ExtensionDelegate.cs
arquivo na versão watchOS 3 do aplicativo de treino incluiria o seguinte código:
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
O OutdoorRunDelegate.cs
arquivo na versão watchOS 3 do aplicativo de treino incluiria o seguinte código:
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
}
}
Práticas Recomendadas
A Apple sugere o uso das seguintes práticas recomendadas ao projetar e implementar aplicativos de treino no watchOS 3 e iOS 10:
- Certifique-se de que o aplicativo watchOS 3 Workout ainda esteja funcional mesmo quando não conseguir se conectar ao iPhone e à versão iOS 10 do aplicativo.
- Use a distância do HealthKit quando o GPS não estiver disponível, pois é capaz de gerar amostras de distância sem GPS.
- Permita que o usuário inicie o treino a partir do Apple Watch ou do iPhone.
- Permita que o aplicativo exiba exercícios de outras fontes (como outros aplicativos de terceiros 3rd) em suas exibições de dados históricos.
- Certifique-se de que o aplicativo não exiba treinos excluídos em dados históricos.
Resumo
Este artigo abordou as melhorias que a Apple fez para treinar aplicativos no watchOS 3 e como implementá-los no Xamarin.