watchOS Workout Apps in Xamarin (Aplikacje treningowe dla systemu watchOS na platformie Xamarin)
W tym artykule omówiono ulepszenia wprowadzone przez firmę Apple w celu trenowania aplikacji w systemie watchOS 3 i sposobu ich implementowania na platformie Xamarin.
Nowe, aby obejrzeć systemOS 3, aplikacje związane z treningiem mają możliwość uruchamiania w tle na zegarku Apple Watch i uzyskiwania dostępu do danych zestawu HealthKit. Ich nadrzędna aplikacja oparta na systemie iOS 10 ma również możliwość uruchamiania aplikacji opartej na systemie watchOS 3 bez interwencji użytkownika.
Poniższe tematy zostaną szczegółowo omówione:
Informacje o aplikacjach treningowych
Użytkownicy aplikacji fitness i treningu mogą być bardzo dedykowani, poświęcając kilka godzin dnia na cele zdrowotne i fitness. W rezultacie oczekują dynamicznych, łatwych w użyciu aplikacji, które dokładnie zbierają i wyświetlają dane oraz bezproblemowo integrują się z usługą Apple Health.
Dobrze zaprojektowana aplikacja fitness lub treningu pomaga użytkownikom nakreślić swoje działania, aby osiągnąć cele fitness. Korzystając z zegarka Apple Watch, aplikacje fitness i treningu mają natychmiastowy dostęp do tętna, spalania kalorii i wykrywania aktywności.
Nowość w systemie watchOS 3, Background Running zapewnia aplikacjom powiązanym z treningiem możliwość uruchamiania w tle na zegarku Apple Watch i uzyskiwania dostępu do danych zestawu HealthKit.
W tym dokumencie przedstawiono funkcję Uruchamianie w tle, omówiono cykl życia aplikacji treningu i pokazano, jak aplikacja treningowa może współtworzyć pierścienie aktywności użytkownika na zegarku Apple Watch.
Informacje o sesjach treningowych
Sercem każdej aplikacji treningowej jest sesja treningu (HKWorkoutSession
), którą użytkownik może rozpocząć i zatrzymać. Interfejs API sesji treningu jest łatwy do zaimplementowania i zapewnia kilka korzyści dla aplikacji treningowej, takich jak:
- Wykrywanie oparzeń ruchu i kalorii na podstawie typu działania.
- Automatyczne współtworzenie pierścieni działań użytkownika.
- Podczas sesji aplikacja będzie automatycznie wyświetlana za każdym razem, gdy użytkownik wznawia urządzenie (przez podniesienie nadgarstka lub interakcję z zegarkiem Apple Watch).
Informacje o uruchomionym tle
Jak wspomniano powyżej, w przypadku systemu watchOS 3 aplikację do treningu można ustawić tak, aby uruchamiała się w tle. Korzystanie z aplikacji Background Running a workout może przetwarzać dane z czujników zegarka Apple Watch podczas uruchamiania w tle. Na przykład aplikacja może nadal monitorować tętno użytkownika, mimo że nie jest już wyświetlana na ekranie.
Uruchamianie w tle zapewnia również możliwość prezentowania opinii na żywo użytkownikowi w dowolnym momencie podczas aktywnej sesji treningu, takich jak wysyłanie alertu haptycznego w celu poinformowania użytkownika o bieżącym postępie.
Ponadto funkcja Background Running umożliwia aplikacji szybkie aktualizowanie interfejsu użytkownika, dzięki czemu użytkownik ma najnowsze dane, gdy szybko przegląda swój zegarek Apple Watch.
Aby utrzymać wysoką wydajność w zegarku Apple Watch, aplikacja zegarka korzystająca z funkcji Background Running powinna ograniczyć ilość pracy w tle w celu oszczędzania baterii. Jeśli aplikacja używa nadmiernego procesora CPU w tle, może ona być zawieszona przez system watchOS.
Włączanie uruchamiania w tle
Aby włączyć uruchamianie w tle, wykonaj następujące czynności:
W Eksplorator rozwiązań kliknij dwukrotnie plik aplikacji i Telefon
Info.plist
rozszerzenia zegarka, aby otworzyć go do edycji.Przejdź do widoku Źródła :
Dodaj nowy klucz o nazwie
WKBackgroundModes
i ustaw typ naArray
:Dodaj nowy element do tablicy z
String
typem i wartością :workout-processing
Zapisz zmiany w pliku.
Rozpoczynanie sesji treningu
Istnieją trzy główne kroki rozpoczęcia sesji treningu:
- Aplikacja musi zażądać autoryzacji w celu uzyskania dostępu do danych w zestawie HealthKit.
- Utwórz obiekt Konfiguracji treningu dla typu rozpoczętego treningu.
- Utwórz i rozpocznij sesję treningu przy użyciu nowo utworzonej konfiguracji treningu.
Żądanie autoryzacji
Aby aplikacja mogła uzyskać dostęp do danych zestawu HealthKit użytkownika, musi zażądać i otrzymać autoryzację od użytkownika. W zależności od charakteru aplikacji treningowej może ona wykonywać następujące typy żądań:
- Autoryzacja do zapisywania danych:
- Treningi
- Autoryzacja odczytu danych:
- Energia spalona
- Odległość
- Tętna
Aby aplikacja mogła zażądać autoryzacji, należy ją skonfigurować do uzyskiwania dostępu do zestawu HealthKit.
Należy wykonać następujące czynności:
W Eksplorator rozwiązań kliknij
Entitlements.plist
dwukrotnie plik, aby otworzyć go do edycji.Przewiń do dołu i zaznacz pole wyboru Włącz zestaw HealthKit:
Zapisz zmiany w pliku.
Postępuj zgodnie z instrukcjami w sekcjach Jawny identyfikator aplikacji i Profil aprowizacji oraz Kojarzenie identyfikatora aplikacji i profilu aprowizacji z aplikacją platformy Xamarin.iOS w artykule Wprowadzenie do zestawu HealthKit , aby poprawnie aprowizować aplikację.
Na koniec skorzystaj z instrukcji w zestawie kondycji programowania i żądaniu uprawnień od użytkownika artykułu Wprowadzenie do zestawu HealthKit, aby zażądać autoryzacji dostępu do magazynu danych HealthKit użytkownika.
Ustawianie konfiguracji treningu
Sesje treningu są tworzone przy użyciu obiektu konfiguracji treningu (HKWorkoutConfiguration
), który określa typ treningu (np HKWorkoutActivityType.Running
. ) i lokalizację treningu (na przykład HKWorkoutSessionLocationType.Outdoor
):
using HealthKit;
...
// Create a workout configuration
var configuration = new HKWorkoutConfiguration () {
ActivityType = HKWorkoutActivityType.Running,
LocationType = HKWorkoutSessionLocationType.Outdoor
};
Tworzenie delegata sesji treningu
Aby obsłużyć zdarzenia, które mogą wystąpić podczas sesji treningu, aplikacja musi utworzyć wystąpienie delegata sesji treningu. Dodaj nową klasę do projektu i u podstaw ją z HKWorkoutSessionDelegate
klasy . Na przykład przebiegu na świeżym powietrzu może wyglądać następująco:
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
}
}
Ta klasa tworzy kilka zdarzeń, które zostaną zgłoszone jako stan zmiany sesji treningu (DidChangeToState
) i jeśli sesja treningu zakończy się niepowodzeniem (DidFail
).
Tworzenie sesji treningu
Za pomocą konfiguracji treningu i delegata sesji treningu utworzonego powyżej, aby utworzyć nową sesję treningu i uruchomić ją względem domyślnego magazynu HealthKit użytkownika:
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);
}
Jeśli aplikacja rozpocznie tę sesję treningu, a użytkownik zmieni się z powrotem na twarz zegarka, zostanie wyświetlona ikona małego zielonego "uruchomionego człowieka" nad twarzą:
Jeśli użytkownik naciągnie tę ikonę, zostanie on przeniesiony z powrotem do aplikacji.
Zbieranie i kontrola danych
Po skonfigurowaniu i uruchomieniu sesji treningu aplikacja będzie musiała zbierać dane dotyczące sesji (na przykład tętna użytkownika) i kontrolować stan sesji:
- Obserwowanie przykładów — aplikacja będzie musiała pobrać informacje z zestawu HealthKit, które będą działać i wyświetlane użytkownikowi.
- Obserwowanie zdarzeń — aplikacja musi reagować na zdarzenia generowane przez zestaw HealthKit lub z interfejsu użytkownika aplikacji (np. użytkownik wstrzymujący trening).
- Wprowadź stan uruchomienia — sesja została uruchomiona i jest obecnie uruchomiona.
- Wprowadź Stan wstrzymania — użytkownik wstrzymał bieżącą sesję treningu i może go uruchomić ponownie w późniejszym terminie. Użytkownik może przełączać się między stanami uruchomionymi i wstrzymanych kilka razy w jednej sesji treningu.
- Zakończenie sesji treningu — w dowolnym momencie użytkownik może zakończyć sesję treningu lub może wygasnąć i zakończyć samodzielnie, jeśli był to trening mierzony (na przykład dwumilowy przebieg).
Ostatnim krokiem jest zapisanie wyników sesji treningu w magazynie danych HealthKit użytkownika.
Obserwowanie przykładów zestawu HealthKit
Aplikacja musi otworzyć zapytanie o obiekt zakotwiczenia dla każdego z punktów danych zestawu HealthKit, które go interesuje, takich jak tętno lub aktywną energię spaloną. Dla każdego obserwowanego punktu danych należy utworzyć procedurę obsługi aktualizacji, aby przechwycić nowe dane podczas ich wysyłania do aplikacji.
Z tych punktów danych aplikacja może gromadzić sumy (takie jak łączna odległość uruchomienia) i aktualizować interfejs użytkownika zgodnie z potrzebami. Ponadto aplikacja może powiadamiać użytkowników o osiągnięciu określonego celu lub osiągnięcia, takich jak ukończenie następnego przebiegu.
Przyjrzyj się następującego przykładowego kodu:
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);
}
Tworzy predykat, aby ustawić datę początkową, do której ma zostać pobrana dane przy użyciu GetPredicateForSamples
metody . Tworzy zestaw urządzeń do ściągania informacji HealthKit z metody GetPredicateForObjectsFromDevices
, w tym przypadku lokalnego zegarka Apple Watch (HKDevice.LocalDevice
). Dwa predykaty są łączone w predykat złożony (NSCompoundPredicate
) przy użyciu CreateAndPredicate
metody .
Dla żądanego punktu danych zostanie utworzony nowy HKAnchoredObjectQuery
(w tym przypadku HKQuantityTypeIdentifier.ActiveEnergyBurned
dla punktu danych aktywnej energii spalonej), żaden limit nie zostanie nałożony na ilość zwracanych danych (HKSampleQuery.NoLimit
), a program obsługi aktualizacji jest definiowany w celu obsługi danych zwracanych do aplikacji z zestawu HealthKit.
Program obsługi aktualizacji będzie wywoływany za każdym razem, gdy nowe dane zostaną dostarczone do aplikacji dla danego punktu danych. Jeśli żaden błąd nie zostanie zwrócony, aplikacja będzie mogła bezpiecznie odczytać dane, wykonać wymagane obliczenia i zaktualizować interfejs użytkownika zgodnie z potrzebami.
Kod jest zapętlany dla wszystkich przykładów (HKSample
) zwracanych w addedObjects
tablicy i rzutuje je do próbki Quantity (HKQuantitySample
). Następnie pobiera podwójną wartość próbki jako dżule (HKUnit.Joule
) i gromadzi ją w sumie uruchomionej aktywnej energii spalonej dla treningu i aktualizuje interfejs użytkownika.
Powiadomienie osiągniętego celu
Jak wspomniano powyżej, gdy użytkownik osiągnie cel w aplikacji treningu (na przykład ukończenie pierwszej mili przebiegu), może wysłać do użytkownika haptyczne opinie za pośrednictwem aparatu Taptic. Aplikacja powinna również zaktualizować interfejs użytkownika w tym momencie, ponieważ użytkownik będzie bardziej niż prawdopodobnie podnieść nadgarstka, aby zobaczyć zdarzenie, które skłoniło opinię.
Aby odtworzyć opinie haptyczne, użyj następującego kodu:
// Play haptic feedback
WKInterfaceDevice.CurrentDevice.PlayHaptic (WKHapticType.Notification);
Obserwowanie zdarzeń
Zdarzenia to znaczniki czasu, których aplikacja może używać do wyróżniania niektórych punktów podczas treningu użytkownika. Niektóre zdarzenia zostaną utworzone bezpośrednio przez aplikację i zapisane w treningu, a niektóre wydarzenia zostaną utworzone automatycznie przez zestaw HealthKit.
Aby obserwować zdarzenia tworzone przez zestaw HealthKit, aplikacja zastąpi metodę DidGenerateEvent
elementu 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;
}
}
Firma Apple dodała następujące nowe typy zdarzeń w systemie watchOS 3:
HKWorkoutEventType.Lap
- Są dla wydarzeń, które podział treningu na równe części odległości. Na przykład, aby oznaczyć jedno okrążenie wokół toru podczas biegania.HKWorkoutEventType.Marker
- Są dla dowolnych punktów orientacyjnych w treningu. Na przykład dotarcie do określonego punktu na trasie biegu na świeżym powietrzu.
Te nowe typy mogą być tworzone przez aplikację i przechowywane w treningu do późniejszego użycia w tworzeniu grafów i statystyk.
Aby utworzyć zdarzenie znacznika, wykonaj następujące czynności:
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);
}
Ten kod tworzy nowe wystąpienie zdarzenia znacznika (HKWorkoutEvent
) i zapisuje je w prywatnej kolekcji zdarzeń (które zostaną później zapisane w sesji treningu) i powiadamia użytkownika zdarzenia za pośrednictwem haptyki.
Wstrzymując i wznawiając treningi
W dowolnym momencie sesji treningu użytkownik może tymczasowo wstrzymać trening i wznowić go w późniejszym czasie. Mogą na przykład wstrzymać przebieg wewnątrz, aby wykonać ważne wywołanie i wznowić przebieg po zakończeniu połączenia.
Interfejs użytkownika aplikacji powinien zapewnić sposób wstrzymania i wznowienia treningu (przez wywołanie zestawu HealthKit), aby zegarek Apple Watch mógł zaoszczędzić zarówno moc, jak i przestrzeń danych, gdy użytkownik zawiesił swoją aktywność. Ponadto aplikacja powinna ignorować wszystkie nowe punkty danych, które mogą być odbierane, gdy sesja treningu jest w stanie wstrzymania.
Zestaw HealthKit odpowie na wstrzymywanie i wznawianie wywołań przez wygenerowanie zdarzeń wstrzymywania i wznawiania. Podczas wstrzymania sesji treningu żadne nowe zdarzenia ani dane nie zostaną wysłane do aplikacji przez zestaw kondycji do momentu wznowienia sesji.
Użyj następującego kodu, aby wstrzymać i wznowić sesję treningu:
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);
}
Zdarzenia wstrzymywania i wznawiania, które zostaną wygenerowane na podstawie zestawu HealthKit, mogą być obsługiwane przez zastąpienie DidGenerateEvent
metody :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;
}
}
Zdarzenia ruchu
Nowością w systemie watchOS 3 są zdarzenia Motion Paused (HKWorkoutEventType.MotionPaused
) i Motion Resumed (HKWorkoutEventType.MotionResumed
). Te zdarzenia są wywoływane automatycznie przez zestaw HealthKit podczas treningu uruchomionego, gdy użytkownik zaczyna i przestaje się przenosić.
Gdy aplikacja odbierze zdarzenie Wstrzymane ruch, powinno zatrzymać zbieranie danych do momentu wznowienia ruchu przez użytkownika i odebrania zdarzenia Motion Resumes. Aplikacja nie powinna wstrzymać sesji treningu w odpowiedzi na zdarzenie Wstrzymane ruch.
Ważne
Zdarzenia wstrzymywania ruchu i wznawiania ruchu są obsługiwane tylko dla typu działania RunningWorkout (HKWorkoutActivityType.Running
).
Ponownie te zdarzenia można obsłużyć przez zastąpienie DidGenerateEvent
metody metody 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;
}
}
Kończenie i zapisywanie sesji treningu
Gdy użytkownik ukończy trening, aplikacja będzie musiała zakończyć bieżącą sesję treningu i zapisać ją w bazie danych HealthKit. Treningi zapisane w zestawie HealthKit będą automatycznie wyświetlane na liście działań treningu.
Nowość w systemie iOS 10, w tym lista aktywności treningu na i Telefon użytkownika. Więc nawet jeśli Apple Watch nie jest w pobliżu, trening zostanie przedstawiony w telefonie.
Treningi, które obejmują próbki energii, zaktualizują pierścień przenoszenia użytkownika w aplikacji Działania, aby aplikacje innych firm mogły teraz przyczynić się do codziennych celów przenoszenia użytkownika.
Aby zakończyć i zapisać sesję treningu, wymagane są następujące kroki:
- Najpierw aplikacja będzie musiała zakończyć sesję treningu.
- Sesja treningu jest zapisywana w zestawie HealthKit.
- Dodaj wszystkie próbki (takie jak spalone energia lub odległość) do zapisanej sesji treningu.
Kończenie sesji
Aby zakończyć sesję treningu, wywołaj metodę EndWorkoutSession
HKHealthStore
przekazywania elementu HKWorkoutSession
:
public HKHealthStore HealthStore { get; private set; }
public HKWorkoutSession WorkoutSession { get; private set;}
...
public void EndOutdoorRun ()
{
// End the current workout session
HealthStore.EndWorkoutSession (WorkoutSession);
}
Spowoduje to zresetowanie czujników urządzeń do trybu normalnego. Po zakończeniu treningu zestaw HealthKit otrzyma wywołanie zwrotne DidChangeToState
metody :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;
}
}
Zapisywanie sesji
Po zakończeniu sesji treningu aplikacja będzie musiała utworzyć trening (HKWorkout
) i zapisać go (wraz ze zdarzeniami) w magazynie danych 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
}
});
}
Ten kod tworzy łączną ilość energii spalonej i odległość treningu jako HKQuantity
obiektów. Zostanie utworzony słownik metadanych definiujących trening i określono lokalizację treningu:
metadata.Add (new NSString ("HKMetadataKeyIndoorWorkout"), new NSString ("NO"));
Nowy HKWorkout
obiekt jest tworzony z taką samą wartością HKWorkoutActivityType
HKWorkoutSession
jak daty początkowe i końcowe, lista zdarzeń (skumulowana z powyższych sekcji), spalonej energii, całkowitej odległości i słownika metadanych. Ten obiekt jest zapisywany w magazynie kondycji i są obsługiwane wszelkie błędy.
Dodawanie przykładów
Gdy aplikacja zapisuje zestaw próbek do treningu, zestaw HealthKit generuje połączenie między próbkami a samym treningiem, aby aplikacja mogła wysyłać zapytania do zestawu HealthKit w późniejszym terminie dla wszystkich próbek skojarzonych z danym treningiem. Korzystając z tych informacji, aplikacja może generować wykresy na podstawie danych treningu i wykreślić je na osi czasu treningu.
Aby aplikacja współtworzyła pierścień przenoszenia aplikacji aktywności, musi zawierać próbki energii z zapisanym treningiem. Ponadto sumy odległości i energii muszą być zgodne z sumą wszystkich próbek, które aplikacja kojarzy z zapisanym treningiem.
Aby dodać próbki do zapisanego treningu, wykonaj następujące czynności:
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
}
});
}
Opcjonalnie aplikacja może obliczyć i utworzyć mniejszy podzbiór próbek lub jedną mega próbkę (obejmującą cały zakres treningu), który następnie zostanie skojarzony z zapisanym treningiem.
Treningi i iOS 10
Każda aplikacja treningowa dla systemu watchOS 3 ma nadrzędną aplikację treningową opartą na systemie iOS 10 i, nową dla systemu iOS 10, tę aplikację dla systemu iOS można użyć do rozpoczęcia treningu, który umieści zegarek Apple Watch w trybie treningu (bez interwencji użytkownika) i uruchom aplikację watchOS w trybie uruchamiania w tle (zobacz Informacje o tle uruchomionym powyżej, aby uzyskać więcej szczegółów).
Gdy aplikacja watchOS jest uruchomiona, może używać funkcji Watch Połączenie ivity na potrzeby obsługi komunikatów i komunikacji z nadrzędną aplikacją systemu iOS.
Zobacz, jak działa ten proces:
- Aplikacja i Telefon tworzy
HKWorkoutConfiguration
obiekt i ustawia typ treningu i lokalizację. - Obiekt
HKWorkoutConfiguration
jest wysyłany do wersji aplikacji Apple Watch i, jeśli nie jest jeszcze uruchomiony, jest uruchamiany przez system. - Korzystając z przekazanej konfiguracji treningu, aplikacja watchOS 3 uruchamia nową sesję treningu (
HKWorkoutSession
).
Ważne
Aby aplikacja nadrzędna i Telefon mogła rozpocząć trening na zegarku Apple Watch, aplikacja watchOS 3 musi mieć włączoną funkcję Uruchamianie w tle. Aby uzyskać więcej informacji, zobacz Włączanie uruchamiania w tle powyżej.
Ten proces jest bardzo podobny do procesu uruchamiania sesji treningu bezpośrednio w aplikacji systemu watchOS 3. W pliku i Telefon użyj następującego kodu:
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
...
}
});
}
}
Ten kod gwarantuje, że zainstalowano wersję aplikacji dla systemu watchOS, a wersja systemu i Telefon może najpierw nawiązać z nią połączenie:
if (ConnectivitySession.ActivationState == WCSessionActivationState.Activated && ConnectivitySession.WatchAppInstalled) {
...
}
Następnie tworzy jak HKWorkoutConfiguration
zwykle i używa StartWatchApp
metody HKHealthStore
, aby wysłać ją do zegarka Apple Watch i uruchomić aplikację i sesję treningu.
W aplikacji systemu operacyjnego zegarka użyj następującego kodu w pliku 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);
}
Pobiera element HKWorkoutConfiguration
i tworzy nowe HKWorkoutSession
i dołącza wystąpienie niestandardowego HKWorkoutSessionDelegate
elementu . Sesja treningu jest uruchamiana względem sklepu HealthKit Health Store użytkownika.
Łączenie wszystkich elementów
Biorąc wszystkie informacje przedstawione w tym dokumencie, aplikacja treningowa oparta na systemie watchOS 3 i jej nadrzędna aplikacja treningowa systemu iOS 10 może obejmować następujące części:
- iOS 10
ViewController.cs
— obsługuje początek sesji Połączenie ivity zegarka i treningu na Zegarku Apple Watch. - watchOS 3
ExtensionDelegate.cs
— obsługuje wersję aplikacji do treningu w systemie watchOS 3. - watchOS 3
OutdoorRunDelegate.cs
— niestandardowyHKWorkoutSessionDelegate
do obsługi wydarzeń na potrzeby treningu.
Ważne
Kod przedstawiony w poniższych sekcjach zawiera tylko części wymagane do zaimplementowania nowych, rozszerzonych funkcji udostępnianych aplikacjom Trening w systemie watchOS 3. Cały kod pomocniczy i kod do prezentowania i aktualizowania interfejsu użytkownika nie są uwzględniane, ale można je łatwo utworzyć, postępując zgodnie z naszą inną dokumentacją systemu watchOS.
ViewController.cs
Plik ViewController.cs
w nadrzędnej wersji aplikacji treningowej systemu iOS 10 zawiera następujący kod:
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
Plik ExtensionDelegate.cs
w aplikacji do treningu w systemie watchOS 3 zawiera następujący kod:
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
Plik OutdoorRunDelegate.cs
w aplikacji do treningu w systemie watchOS 3 zawiera następujący kod:
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
}
}
Najlepsze praktyki
Firma Apple sugeruje stosowanie następujących najlepszych rozwiązań podczas projektowania i implementowania aplikacji treningowych w systemach watchOS 3 i iOS 10:
- Upewnij się, że aplikacja watchOS 3 Workout jest nadal funkcjonalna nawet wtedy, gdy nie może nawiązać połączenia z aplikacją i Telefon i wersją systemu iOS 10.
- Użyj odległości HealthKit, gdy GPS jest niedostępny, ponieważ jest w stanie wygenerować próbki odległości bez GPS.
- Zezwól użytkownikowi na rozpoczęcie treningu z zegarka Apple Watch lub i Telefon.
- Zezwalaj aplikacji na wyświetlanie treningów z innych źródeł (takich jak inne aplikacje innych firm) w widokach danych historycznych.
- Upewnij się, że aplikacja nie wyświetla usuniętych treningów w danych historycznych.
Podsumowanie
W tym artykule omówiono ulepszenia, które firma Apple wprowadziła w celu trenowania aplikacji w systemie watchOS 3 i jak zaimplementować je na platformie Xamarin.