Поделиться через


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.

Включение фонового выполнения

Чтобы включить фоновый запуск, сделайте следующее:

  1. В Обозреватель решений дважды щелкните компаньон расширения watch i Телефон файл приложенияInfo.plist, чтобы открыть его для редактирования.

  2. Перейдите в представление исходного кода.

    Представление источника

  3. Добавьте новый ключ с именем WKBackgroundModes и задайте для типаArray:

    Добавление нового ключа с именем W КБ ackgroundModes

  4. Добавьте новый элемент в массив с типом String и значениемworkout-processing:

    Добавление нового элемента в массив с типом строки и значением обработки тренировки

  5. Сохраните изменения в файле.

Запуск сеанса тренировки

Существует три основных шага для запуска сеанса workout:

Три основных шага по запуску сеанса тренировки

  1. Приложение должно запросить авторизацию для доступа к данным в HealthKit.
  2. Создайте объект конфигурации Workout для типа запущенной тренировки.
  3. Создайте и запустите сеанс workout с помощью созданной конфигурации workout.

Запрос авторизации

Прежде чем приложение сможет получить доступ к данным HealthKit пользователя, он должен запрашивать и получать авторизацию от пользователя. В зависимости от характера приложения для тренировки он может выполнять следующие типы запросов:

  • Авторизация для записи данных:
    • Тренировки
  • Авторизация для чтения данных:
    • Энергия сгорела
    • расстояние;
    • Частота пульса

Прежде чем приложение может запросить авторизацию, его необходимо настроить для доступа к HealthKit.

Выполните следующие действия.

  1. В обозревателе решений дважды щелкните файл Entitlements.plist, чтобы открыть его для редактирования.

  2. Прокрутите страницу вниз и проверка Enable HealthKit:

    Проверка включения HealthKit

  3. Сохраните изменения в файле.

  4. Следуйте инструкциям в разделах "Явный идентификатор приложения" и "Профиль подготовки" и "Сопоставление идентификатора приложения" и "Профиль подготовки" с разделами "Приложение Xamarin.iOS" статьи "Введение в HealthKit" для правильной подготовки приложения.

  5. Наконец, используйте инструкции в наборе работоспособности программирования и запрос разрешения от пользователейстатьи "Введение в 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 приложение потребуется собрать данные о сеансе (например, частоте пульса пользователя) и контролировать состояние сеанса:

Схема сбора и управления данными

  1. Наблюдение за примерами . Приложению потребуется получить сведения из HealthKit, которые будут действовать и отображаться пользователю.
  2. Наблюдение за событиями . Приложению потребуется реагировать на события, созданные HealthKit или из пользовательского интерфейса приложения (например, пользователь, не использующее тренировку).
  3. Введите состояние выполнения — сеанс был запущен и в настоящее время запущен.
  4. Введите приостановленное состояние . Пользователь приостановил текущий сеанс тренировки и может перезапустить его на более позднюю дату. Пользователь может переключаться между запущенным и приостановленным состояниями несколько раз в одном сеансе workout.
  5. Сеанс завершения тренировки . В любой момент пользователь может завершить сеанс 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 не находится рядом, тренировка будет представлена на телефоне.

Тренировки, включающие энергетические примеры, обновят кольцо перемещения пользователя в приложении "Действия", чтобы сторонние приложения теперь могут внести свой вклад в ежедневные цели перемещения пользователя.

Для завершения и сохранения сеанса тренировки необходимо выполнить следующие действия.

Завершение и сохранение схемы сеанса тренировки

  1. Во-первых, приложению потребуется завершить сеанс тренировки.
  2. Сеанс workout сохраняется в HealthKit.
  3. Добавьте все примеры (например, энергию, сожженную или расстояние) в сохраненный сеанс тренировки.

Завершение сеанса

Чтобы завершить сеанс 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 завершит работу, он получит обратный вызов методу DidChangeToStateHKWorkoutSessionDelegate:

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 Телефон и схема связи Apple Watch

  1. Приложение i Телефон создает HKWorkoutConfiguration объект и задает тип и расположение workout.
  2. Объект HKWorkoutConfiguration отправляется в версию приложения Apple Watch и, если она еще не запущена, она запускается системой.
  3. Используя переданную в конфигурации 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 могут включать следующие части:

  1. iOS 10 ViewController.cs — обрабатывает начало сеанса Подключение ivity и тренировки в Apple Watch.
  2. watchOS 3 — обрабатывает версию приложения для тренировки watchOS 3 ExtensionDelegate.cs .
  3. 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.