WatchOS Workout Apps в Xamarin
В этой статье рассматриваются усовершенствования Apple, сделанные для тренировки приложений в watchOS 3 и их реализации в Xamarin.
Новые возможности для просмотраOS 3, связанные с тренировками приложения имеют возможность выполняться в фоновом режиме в Apple Watch и получать доступ к данным HealthKit. Их родительское приложение на основе iOS 10 также имеет возможность запускать приложение на основе watchOS 3 без вмешательства пользователя.
Будут подробно рассмотрены следующие темы:
Сведения о приложениях для тренировки
Пользователи приложений для фитнеса и тренировки могут быть высоко выделенными, занимаясь несколькими часами дня в направлении их здоровья и фитнес-целей. В результате они ожидают быстрых и простых в использовании приложений, которые точно собирают и отображают данные и легко интегрируются с Apple Health.
Хорошо разработанное приложение для фитнеса или тренировки помогает пользователям провести диаграмму своих действий, чтобы достичь своих целей в фитнесе. Используя Apple Watch, приложения для фитнеса и тренировки имеют мгновенный доступ к частоте пульса, сжиганию калорий и обнаружению активности.
Новые возможности для просмотраOS 3, фоновый запуск дает возможность работать в фоновом режиме в Apple Watch и получать доступ к данным HealthKit.
В этом документе будет представлена функция фонового выполнения, охватить жизненный цикл приложения тренировки и показать, как приложение для тренировки может внести свой вклад в круги действий пользователя в Apple Watch.
Сведения о сеансах тренировки
Сердцем каждого приложения тренировки является сеанс тренировки (HKWorkoutSession
), который пользователь может запустить и остановить. API сеанса тренировки легко реализовать и предоставляет несколько преимуществ для приложения тренировки, например:
- Обнаружение движения и сжигания калорий на основе типа действия.
- Автоматический вклад в кольца действий пользователя.
- Во время сеанса приложение автоматически будет отображаться при каждом пробуждении устройства (путем поднятия запястья или взаимодействия с Apple Watch).
О фоновом выполнении
Как упоминалось выше, при использовании watchOS 3 можно установить приложение для тренировки в фоновом режиме. Использование фонового запуска приложения для тренировки может обрабатывать данные с датчиков Apple Watch во время работы в фоновом режиме. Например, приложение может продолжать отслеживать частоту пульса пользователя, даже если он больше не отображается на экране.
Фоновый запуск также предоставляет возможность представить динамическую обратную связь пользователю в любое время во время активного сеанса Workout, например отправку хаптичного оповещения, чтобы сообщить пользователю о текущем ходе выполнения.
Кроме того, фоновый запуск позволяет приложению быстро обновлять пользовательский интерфейс, чтобы пользователь получил последние данные, когда они быстро смотрят на Apple Watch.
Чтобы обеспечить высокую производительность в Apple Watch, приложение наблюдения с помощью фоновой работы должно ограничить объем фоновой работы для экономии батареи. Если приложение использует чрезмерный ЦП в фоновом режиме, оно может быть приостановлено в watchOS.
Включение фонового выполнения
Чтобы включить фоновый запуск, сделайте следующее:
В Обозреватель решений дважды щелкните компаньон расширения watch i Телефон файл приложения
Info.plist
, чтобы открыть его для редактирования.Перейдите в представление исходного кода.
Добавьте новый ключ с именем
WKBackgroundModes
и задайте для типаArray
:Добавьте новый элемент в массив с типом
String
и значениемworkout-processing
:Сохраните изменения в файле.
Запуск сеанса тренировки
Существует три основных шага для запуска сеанса workout:
- Приложение должно запросить авторизацию для доступа к данным в HealthKit.
- Создайте объект конфигурации Workout для типа запущенной тренировки.
- Создайте и запустите сеанс workout с помощью созданной конфигурации workout.
Запрос авторизации
Прежде чем приложение сможет получить доступ к данным HealthKit пользователя, он должен запрашивать и получать авторизацию от пользователя. В зависимости от характера приложения для тренировки он может выполнять следующие типы запросов:
- Авторизация для записи данных:
- Тренировки
- Авторизация для чтения данных:
- Энергия сгорела
- расстояние;
- Частота пульса
Прежде чем приложение может запросить авторизацию, его необходимо настроить для доступа к HealthKit.
Выполните следующие действия.
В обозревателе решений дважды щелкните файл
Entitlements.plist
, чтобы открыть его для редактирования.Прокрутите страницу вниз и проверка Enable HealthKit:
Сохраните изменения в файле.
Следуйте инструкциям в разделах "Явный идентификатор приложения" и "Профиль подготовки" и "Сопоставление идентификатора приложения" и "Профиль подготовки" с разделами "Приложение Xamarin.iOS" статьи "Введение в HealthKit" для правильной подготовки приложения.
Наконец, используйте инструкции в наборе работоспособности программирования и запрос разрешения от пользователейстатьи "Введение в HealthKit", чтобы запросить авторизацию для доступа к хранилищу данных HealthKit пользователя.
Настройка конфигурации тренировки
Сеансы workout создаются с помощью объекта конфигурации workout (HKWorkoutConfiguration
), указывающего тип тренировки (например HKWorkoutActivityType.Running
) и расположение тренировки (например HKWorkoutSessionLocationType.Outdoor
: ):
using HealthKit;
...
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
Создание делегата сеанса тренировки
Чтобы обрабатывать события, которые могут возникать во время сеанса workout, приложению потребуется создать экземпляр делегата сеанса workout. Добавьте новый класс в проект и наведите его на HKWorkoutSessionDelegate
основе класса. Пример запуска на открытом воздухе может выглядеть следующим образом:
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
}
}
Этот класс создает несколько событий, которые будут вызваны как состояние изменений сеанса workout (DidChangeToState
) и если сеанс тренировки завершается сбоем (DidFail
).
Создание сеанса тренировки
Использование делегата сеанса workout и workout session, созданного выше, для создания нового сеанса Workout и запуска его в хранилище HealthKit пользователя по умолчанию:
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);
}
Если приложение запускает этот сеанс Workout, и пользователь переключается обратно на лицо наблюдения, будет отображаться крошечный зеленый значок "запущенный человек" над лицом:
Если пользователь нажимает этот значок, он будет возвращен в приложение.
Сбор и управление данными
После настройки и запуска сеанса workout приложение потребуется собрать данные о сеансе (например, частоте пульса пользователя) и контролировать состояние сеанса:
- Наблюдение за примерами . Приложению потребуется получить сведения из HealthKit, которые будут действовать и отображаться пользователю.
- Наблюдение за событиями . Приложению потребуется реагировать на события, созданные HealthKit или из пользовательского интерфейса приложения (например, пользователь, не использующее тренировку).
- Введите состояние выполнения — сеанс был запущен и в настоящее время запущен.
- Введите приостановленное состояние . Пользователь приостановил текущий сеанс тренировки и может перезапустить его на более позднюю дату. Пользователь может переключаться между запущенным и приостановленным состояниями несколько раз в одном сеансе workout.
- Сеанс завершения тренировки . В любой момент пользователь может завершить сеанс workout или он может истекать и заканчиваться самостоятельно, если это была лимитная тренировка (например, двухмиловый запуск).
Последним шагом является сохранение результатов сеанса Workout в хранилище данных HealthKit пользователя.
Наблюдение за примерами HealthKit
Приложению потребуется открыть запрос объекта привязки для каждой из точек данных HealthKit, которые он заинтересован, например пульса или активной энергии, сожженной. Для каждой наблюдаемой точки данных необходимо создать обработчик обновления для записи новых данных при отправке в приложение.
Из этих точек данных приложение может накапливать итоги (например, общее расстояние выполнения) и обновлять пользовательский интерфейс по мере необходимости. Кроме того, приложение может уведомить пользователей о достижении определенной цели или достижения, например о завершении следующей мили запуска.
Ознакомьтесь со следующим примером кода:
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);
}
Он создает предикат для задания начальной даты, для получения данных для использования GetPredicateForSamples
метода. Он создает набор устройств для извлечения сведений HealthKit из GetPredicateForObjectsFromDevices
метода, в этом случае локальный Apple Watch (толькоHKDevice.LocalDevice
). Два предиката объединяются в составной предикат (NSCompoundPredicate
) с помощью CreateAndPredicate
метода.
Новое HKAnchoredObjectQuery
создается для нужной точки данных (в данном случае HKQuantityTypeIdentifier.ActiveEnergyBurned
для точки данных Active Energy Burned), ограничение не накладывается на объем возвращаемых () данных,HKSampleQuery.NoLimit
а обработчик обновления определяется для обработки данных, возвращаемых приложению из HealthKit.
Обработчик обновления будет вызываться в любое время доставки новых данных в приложение для заданной точки данных. Если ошибка не возвращается, приложение может безопасно считывать данные, выполнять любые необходимые вычисления и обновлять пользовательский интерфейс по мере необходимости.
Код цикличен по всем образцам (HKSample
), возвращаемым в массиве addedObjects
, и приводит их к выборке количества (HKQuantitySample
). Затем он получает двойное значение образца в виде джоуля (HKUnit.Joule
) и накапливает его в общей сложности активной энергии, сожженной для тренировки и обновляет пользовательский интерфейс.
Уведомление о достижении цели
Как упоминание выше, когда пользователь достигает цели в приложении тренировки (например, завершение первой мили выполнения), он может отправлять хиплитые отзывы пользователю через taptic Engine. Приложение также должно обновить пользовательский интерфейс на этом этапе, так как пользователь, скорее всего, поднимет запястье, чтобы увидеть событие, которое побудило отзыв.
Чтобы воспроизвести тактическую обратную связь, используйте следующий код:
// Play haptic feedback
WKInterfaceDevice.CurrentDevice.PlayHaptic (WKHapticType.Notification);
Наблюдение за событиями
События — это метки времени, которые приложение может использовать для выделения определенных точек во время тренировки пользователя. Некоторые события будут созданы непосредственно приложением и сохранены в тренировке, а некоторые события будут автоматически созданы HealthKit.
Чтобы наблюдать за событиями, созданными HealthKit, приложение переопределит DidGenerateEvent
метод 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 добавила следующие новые типы событий в watchOS 3:
HKWorkoutEventType.Lap
— Предназначены для событий, которые разбивают тренировку на равные расстояния. Например, для маркировки одного круга вокруг дорожки во время выполнения.HKWorkoutEventType.Marker
- Предназначены для произвольных точек интереса в рамках тренировки. Например, достижение определенной точки на маршруте открытого запуска.
Эти новые типы можно создать приложением и сохранить в тренировке для последующего использования при создании графов и статистики.
Чтобы создать событие маркера, сделайте следующее:
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);
}
Этот код создает новый экземпляр события маркера (HKWorkoutEvent
) и сохраняет его в частную коллекцию событий (которая позже будет записана в сеанс workout) и уведомляет пользователя о событии с помощью хаптичек.
Приостановка и возобновление тренировки
В любой момент в сеансе тренировки пользователь может временно приостановить тренировку и возобновить его позже. Например, они могут приостановить запуск в помещении, чтобы принять важный вызов и возобновить запуск после завершения вызова.
Пользовательский интерфейс приложения должен предоставить способ приостановки и возобновления тренировки (путем вызова в HealthKit), чтобы Apple Watch может сохранить как питание, так и пространство данных, пока пользователь приостановил свою активность. Кроме того, приложение должно игнорировать любые новые точки данных, которые могут быть получены, когда сеанс тренировки находится в приостановленном состоянии.
HealthKit будет реагировать на приостановку и возобновление вызовов путем создания событий приостановки и возобновления работы. Пока сеанс тренировки приостановлен, новые события или данные не будут отправляться в приложение HealthKit до возобновления сеанса.
Используйте следующий код для приостановки и возобновления сеанса тренировки.
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);
}
События приостановки и возобновления, создаваемые из HealthKit, можно обрабатывать, переопределяя DidGenerateEvent
метод: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;
}
}
События движения
Кроме того, новые для просмотраOS 3, являются события приостановки движения (HKWorkoutEventType.MotionPaused
) и возобновления движения (HKWorkoutEventType.MotionResumed
). Эти события автоматически создаются HealthKit во время выполнения тренировки при запуске и остановке перемещения пользователя.
Когда приложение получает событие приостановки движения, оно должно прекратить сбор данных до тех пор, пока пользователь не возобновляет движение и событие "Возобновление движения" будет получено. Приложение не должно приостановить сеанс тренировки в ответ на событие приостановки движения.
Внимание
События приостановки движения и возобновления движения поддерживаются только для типа действия RunningWorkout (HKWorkoutActivityType.Running
).
Опять же, эти события можно обрабатывать путем переопределения DidGenerateEvent
метода 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;
}
}
Завершение и сохранение сеанса тренировки
Когда пользователь завершил работу, приложение потребуется завершить текущий сеанс Workout и сохранить его в базе данных HealthKit. Рабочие нагрузки, сохраненные в HealthKit, автоматически будут отображаться в списке действий workout.
Новые возможности iOS 10 включают в себя список действий workout в i Телефон пользователя. Так что даже если Apple Watch не находится рядом, тренировка будет представлена на телефоне.
Тренировки, включающие энергетические примеры, обновят кольцо перемещения пользователя в приложении "Действия", чтобы сторонние приложения теперь могут внести свой вклад в ежедневные цели перемещения пользователя.
Для завершения и сохранения сеанса тренировки необходимо выполнить следующие действия.
- Во-первых, приложению потребуется завершить сеанс тренировки.
- Сеанс workout сохраняется в HealthKit.
- Добавьте все примеры (например, энергию, сожженную или расстояние) в сохраненный сеанс тренировки.
Завершение сеанса
Чтобы завершить сеанс workout, вызовите EndWorkoutSession
метод передачи HKHealthStore
в :HKWorkoutSession
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
...
public void EndOutdoorRun ()
{
// End the current workout session
HealthStore.EndWorkoutSession (WorkoutSession);
}
Это приведет к сбросу датчиков устройств в обычный режим. Когда HealthKit завершит работу, он получит обратный вызов методу DidChangeToState
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;
}
}
Сохранение сеанса
После завершения сеанса Workout приложение потребуется создать workout () и сохранить его (HKWorkout
вместе с событиями) в хранилище данных 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
}
});
}
Этот код создает общий объем энергии, сгоревшего и расстояния для тренировки в качестве HKQuantity
объектов. Создается словарь метаданных, определяющих тренировку, и указывается расположение тренировки:
metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));
Новый HKWorkout
объект создается так же HKWorkoutActivityType
, как HKWorkoutSession
и дата начала и окончания, список событий (накапливается из приведенных выше разделов), энергию, сожженную, общую дистанцию и словарь метаданных. Этот объект сохраняется в хранилище работоспособности и все ошибки, обрабатываемые.
Добавление примеров
Когда приложение сохраняет набор примеров в workout, HealthKit создает соединение между примерами и самой работой, чтобы приложение ранее ему было запрашивать HealthKit на более позднюю дату для всех примеров, связанных с данной тренировкой. Используя эти сведения, приложение может создавать графики из данных тренировки и создавать их на основе временная шкала тренировки.
Чтобы приложение внесло свой вклад в кольцо перемещения приложения действий, оно должно включать в себя энергетические образцы с сохраненным тренировкой. Кроме того, итоги для расстояния и энергии должны соответствовать сумме всех примеров, которые приложение связывает с сохраненной тренировкой.
Чтобы добавить примеры в сохраненную тренировку, сделайте следующее:
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
}
});
}
При необходимости приложение может вычислить и создать меньшее подмножество примеров или один мега (охватывающий весь диапазон тренировки), который затем будет связан с сохраненным тренировкой.
Тренировки и iOS 10
Каждое приложение для тренировки watchOS 3 имеет родительское приложение на основе iOS 10, а новое для iOS 10, это приложение iOS можно использовать для запуска тренировки, которая будет размещать Apple Watch в режиме тренировки (без вмешательства пользователя) и запускать приложение watchOS в фоновом режиме выполнения (см . дополнительные сведения о фоновом выполнении выше).
Хотя приложение watchOS запущено, оно может использовать Watch Подключение ivity для обмена сообщениями и обмена данными с родительским приложением iOS.
Ознакомьтесь с тем, как работает этот процесс:
- Приложение i Телефон создает
HKWorkoutConfiguration
объект и задает тип и расположение workout. - Объект
HKWorkoutConfiguration
отправляется в версию приложения Apple Watch и, если она еще не запущена, она запускается системой. - Используя переданную в конфигурации workout, приложение watchOS 3 запускает новый сеанс тренировки (
HKWorkoutSession
).
Внимание
Чтобы родительское приложение i Телефон запустить тренировку в Apple Watch, приложение watchOS 3 должно иметь включенную фоновую работу. Дополнительные сведения см. в разделе "Включение фонового запуска " выше.
Этот процесс очень похож на процесс запуска сеанса Workout в приложении watchOS 3 напрямую. В i Телефон используйте следующий код:
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
...
}
});
}
}
Этот код гарантирует, что установлена версия приложения watchOS, а версия i Телефон может подключиться к ней сначала:
if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
...
}
Затем он создает HKWorkoutConfiguration
как обычное и использует StartWatchApp
метод отправки HKHealthStore
его в Apple Watch и запустить приложение и сеанс workout.
И в приложении для часовой ОС используйте следующий код в следующем коде 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);
}
Он принимает HKWorkoutConfiguration
и создает новый HKWorkoutSession
и присоединяет экземпляр настраиваемого HKWorkoutSessionDelegate
. Сеанс workout запускается для хранилища работоспособности HealthKit пользователя.
Объединение всех частей
Принимая все сведения, представленные в этом документе, приложение для тренировки на основе watchOS 3 и родительское приложение для тренировки на основе iOS 10 могут включать следующие части:
- iOS 10
ViewController.cs
— обрабатывает начало сеанса Подключение ivity и тренировки в Apple Watch. - watchOS 3 — обрабатывает версию приложения для тренировки watchOS 3
ExtensionDelegate.cs
. - watchOS 3
OutdoorRunDelegate.cs
— пользовательHKWorkoutSessionDelegate
для обработки событий для тренировки.
Внимание
Код, показанный в следующих разделах, содержит только части, необходимые для реализации новых расширенных функций, предоставляемых приложениям Workout в watchOS 3. Весь вспомогательный код и код для представления и обновления пользовательского интерфейса не включен, но можно легко создать, следуя нашей другой документации по watchOS.
ViewController.cs
Файл ViewController.cs
в родительской версии приложения для тренировки iOS 10 будет содержать следующий код:
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
Файл ExtensionDelegate.cs
в версии приложения для тренировки watchOS 3 будет содержать следующий код:
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
Файл OutdoorRunDelegate.cs
в версии приложения для тренировки watchOS 3 будет содержать следующий код:
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
}
}
Рекомендации
Apple предлагает использовать следующие рекомендации при разработке и реализации приложений Workout в watchOS 3 и iOS 10:
- Убедитесь, что приложение WatchOS 3 Workout по-прежнему работает, даже если он не может подключиться к i Телефон и версии приложения iOS 10.
- Используйте расстояние HealthKit, если GPS недоступен, так как он может создавать образцы расстояния без GPS.
- Разрешите пользователю начать тренировку из Apple Watch или i Телефон.
- Разрешите приложению отображать тренировки из других источников (например, других сторонних приложений) в своих представлениях исторических данных.
- Убедитесь, что приложение не отображает удаленные тренировки в исторических данных.
Итоги
В этой статье рассматриваются улучшения Apple, сделанные для тренировки приложений в watchOS 3 и как реализовать их в Xamarin.