-
Notifications
You must be signed in to change notification settings - Fork 3
WorkoutKit
Package
SwiftBindings.Apple.WorkoutKit· Version26.2.6Auto-published fromapple-frameworks/WorkoutKit/WORKOUTKIT-GUIDE.md.
SwiftBindings.Apple.WorkoutKit exposes Apple's WorkoutKit framework to C# through .NET 10's native Swift interop — direct Swift calls, not Objective-C proxy wrappers. WorkoutKit lets you compose structured workouts (warmups, interval blocks, goals, alerts), wrap them in a plan, and schedule them to a paired Apple Watch. This guide maps the Swift workflow to the generated C# surface.
The mental model: build a workout (CustomWorkout, SingleGoalWorkout, PacerWorkout, or SwimBikeRunWorkout) out of steps and goals → wrap it in a WorkoutPlan → schedule it with WorkoutScheduler. Activity types and locations come from HealthKit (HealthKit.HKWorkoutActivityType, HealthKit.HKWorkoutSessionLocationType).
- Requirements & install
- Naming conventions
- Quick start: a custom interval workout
- Goals
- Steps and interval blocks
- Alerts
- Workout types
- Plans
- Scheduling
- Memory & threading
- Reference links
- .NET 10.0+
- iOS 26.2+, macOS 26.2+, Mac Catalyst 26.2+
- macOS host for development
- A paired Apple Watch with your companion watchOS app installed for scheduling to actually deliver
dotnet add package SwiftBindings.Apple.WorkoutKit
using WorkoutKit;Many WorkoutKit types also need HealthKit and Foundation unit types, so you will typically also reference HealthKit and Foundation/Swift.Foundation.
| Swift | C# | Rule |
|---|---|---|
func schedule(_:at:) async throws |
ScheduleAsync(workout, at, ct) |
async methods gain Async; argument labels mostly drop, keyword labels (at:) keep a parameter name |
WorkoutScheduler.shared |
WorkoutScheduler.Shared (static property) |
singletons are PascalCase static properties |
enum WorkoutGoal { case distance(...) } |
WorkoutGoal.Distance(...) static factory + .Tag / .CaseTag
|
Swift enums-with-payload project to a class: static factories to build, discriminate with Tag/CaseTag. Only the compound PoolSwimDistanceWithTime case gets a TryGet* unpacker; simple cases are read via the factory + Tag
|
enum IntervalStep.Purpose |
IntervalStep.PurposeType (plain int enum) |
payload-free nested enums become C# enums; Purpose → PurposeType to avoid clashing with the Purpose property |
step.displayName |
step.DisplayName |
properties are PascalCase |
static func supports(activity:) |
SupportsActivity(activity) |
static support-query helpers keep a descriptive name |
Every *Async method also accepts a trailing CancellationToken (defaulted).
using WorkoutKit;
using HealthKit;
// 1. A goal: run for 5 minutes. WorkoutGoal.Time takes a value + a Foundation
// duration unit (from the Foundation binding — e.g. an NSUnitDuration instance).
WorkoutGoal fiveMin = WorkoutGoal.Time(5, durationUnit); // durationUnit: Foundation.NSUnitDuration
// 2. A step toward that goal.
var workStep = new WorkoutStep(fiveMin);
// 3. Wrap the step in an interval block, repeated 4 times.
var block = new IntervalBlock(
new[] { new IntervalStep(IntervalStep.PurposeType.Work, fiveMin) },
iterations: 4);
// 4. A custom workout. activity/location are HealthKit enums (from the
// HealthKit binding) — pick the running activity + outdoor location values.
var workout = new CustomWorkout(
activity: runningActivity, // HealthKit.HKWorkoutActivityType
location: outdoorLocation, // HealthKit.HKWorkoutSessionLocationType
displayName: "4×5 min run",
warmup: workStep,
blocks: new[] { block });
// 5. Wrap in a plan.
var plan = new WorkoutPlan(WorkoutPlan.WorkoutType.Custom(workout), Guid.NewGuid());
// 6. Schedule it (requires authorization + a paired Watch — see "Scheduling").
var when = /* a Swift.Foundation.DateComponents */;
await WorkoutScheduler.Shared.ScheduleAsync(plan, when);WorkoutGoal is a projected Swift enum. Build one with a static factory; read one back with .Tag and TryGet*.
| Factory / member | Signature | |
|---|---|---|
WorkoutGoal.Distance |
WorkoutGoal Distance(double value0, Foundation.NSUnitLength unitLength) |
distance goal |
WorkoutGoal.Time |
WorkoutGoal Time(double value0, Foundation.NSUnitDuration unitDuration) |
duration goal |
WorkoutGoal.Energy |
WorkoutGoal Energy(double value0, Foundation.NSUnitEnergy unitEnergy) |
active-energy goal |
WorkoutGoal.Open |
WorkoutGoal Open (static property) |
open/unbounded goal |
public enum CaseTag : uint { Distance = 0, Time = 1, Energy = 2, PoolSwimDistanceWithTime = 3, Open = 4 }// lengthUnit is a Foundation.NSUnitLength (from the Foundation binding).
var goal = WorkoutGoal.Distance(5, lengthUnit);
if (goal.Tag == WorkoutGoal.CaseTag.Distance) { /* … */ }
WorkoutGoal.Distance/Time/Energytake adoubleplus a Foundation unit type (NSUnitLength/NSUnitDuration/NSUnitEnergy). Those unit types come from the Foundation binding, not WorkoutKit — refer to its surface for the available unit instances.
Reading payloads back uses TryGet*. The pool-swim case (CaseTag.PoolSwimDistanceWithTime) has no factory but is readable:
public bool TryGetPoolSwimDistanceWithTime(
out Swift.Foundation.Measurement<Foundation.NSUnitLength> distance,
out Swift.Foundation.Measurement<Foundation.NSUnitDuration> time);Only
Distance,Time,Energy, andOpenhave constructors/factories in the binding.PoolSwimDistanceWithTimecan be inspected (viaTag/TryGet…) but not constructed from C#.
A single segment of work toward a goal, with an optional alert and display name.
public WorkoutStep();
public WorkoutStep(WorkoutGoal goal);
public WorkoutStep(WorkoutGoal goal, IWorkoutAlert? alert = null);
public WorkoutStep(WorkoutGoal goal, IWorkoutAlert? alert = null, string? displayName = null);Members: Goal (WorkoutGoal), Alert (IWorkoutAlert?), DisplayName (string?).
A step with a purpose — work or recovery — used inside interval blocks.
public IntervalStep(IntervalStep.PurposeType purpose);
public IntervalStep(IntervalStep.PurposeType purpose, WorkoutStep step);
public IntervalStep(IntervalStep.PurposeType purpose, WorkoutGoal goal, IWorkoutAlert? alert = null);
public enum PurposeType : int { Work = 0, Recovery = 1 }Members: Purpose (PurposeType), Step (WorkoutStep).
A repeated sequence of interval steps.
public IntervalBlock();
public IntervalBlock(IEnumerable<IntervalStep> steps);
public IntervalBlock(IEnumerable<IntervalStep> steps, nint iterations = 1);Members: Steps (IReadOnlyList<IntervalStep>), Iterations (int).
Alerts conform to the IWorkoutAlert interface and can be attached to steps. The binding emits these alert types (all implement IWorkoutAlert and value equality):
| Type | Constructor(s) | Key members |
|---|---|---|
HeartRateRangeAlert |
HeartRateRangeAlert(SwiftClosedRange<Measurement<NSUnitFrequency>>) |
Target, Metric, TargetQuantityLowerBound, TargetQuantityUpperBound (HealthKit.HKQuantity) |
HeartRateZoneAlert |
HeartRateZoneAlert(nint zone) |
Zone (int) |
CadenceRangeAlert |
CadenceRangeAlert(SwiftClosedRange<Measurement<NSUnitFrequency>>) |
Metric, TargetQuantityLowerBound, TargetQuantityUpperBound
|
CadenceThresholdAlert |
CadenceThresholdAlert(Measurement<NSUnitFrequency> target) |
Target, Metric, TargetQuantity
|
PowerRangeAlert |
PowerRangeAlert(SwiftClosedRange<Measurement<NSUnitPower>>), (target, WorkoutAlertMetric metric)
|
Metric, TargetQuantityLowerBound, TargetQuantityUpperBound
|
PowerThresholdAlert |
PowerThresholdAlert(Measurement<NSUnitPower> target), (target, WorkoutAlertMetric metric)
|
Target, Metric, TargetQuantity
|
PowerZoneAlert |
PowerZoneAlert(nint zone) |
Zone, Metric
|
SpeedRangeAlert |
SpeedRangeAlert(SwiftClosedRange<Measurement<NSUnitSpeed>>, WorkoutAlertMetric metric) |
Metric, TargetQuantityLowerBound, TargetQuantityUpperBound
|
SpeedThresholdAlert |
SpeedThresholdAlert(Measurement<NSUnitSpeed> target, WorkoutAlertMetric metric) |
Target, Metric, TargetQuantity
|
public enum WorkoutAlertMetric : int { Current = 0, Average = 1 }var alert = new HeartRateZoneAlert(zone: 3);
var step = new WorkoutStep(WorkoutGoal.Open, alert);All four range alert types are fully constructible from C#. The pattern is: build a Swift.Foundation.Measurement<T>(double value, T unit), wrap a lower/upper pair in Swift.SwiftClosedRange<Measurement<T>>(lower, upper), then pass that to the alert constructor. Unit singleton instances (e.g. Foundation.NSUnitFrequency.Hertz, Foundation.NSUnitPower.Watts, Foundation.NSUnitSpeed.MetersPerSecond) come from the Foundation binding:
using var lo = new Measurement<NSUnitFrequency>(60.0, NSUnitFrequency.Hertz);
using var hi = new Measurement<NSUnitFrequency>(150.0, NSUnitFrequency.Hertz);
using var range = new SwiftClosedRange<Measurement<NSUnitFrequency>>(lo, hi);
using var alert = new HeartRateRangeAlert(range);The same pattern applies to CadenceRangeAlert, PowerRangeAlert, and SpeedRangeAlert (substituting NSUnitPower.Watts / NSUnitSpeed.MetersPerSecond as appropriate).
You can ask whether a workout supports a given alert with CustomWorkout.SupportsAlert(IWorkoutAlert alert, HealthKit.HKWorkoutActivityType activity, HealthKit.HKWorkoutSessionLocationType location = Unknown).
Four concrete workout shapes feed a plan:
Fully structured: warmup, interval blocks, cooldown.
public CustomWorkout(HKWorkoutActivityType activity, HKWorkoutSessionLocationType location);
public CustomWorkout(HKWorkoutActivityType activity, HKWorkoutSessionLocationType location, string? displayName);
public CustomWorkout(HKWorkoutActivityType activity, HKWorkoutSessionLocationType location, string? displayName, WorkoutStep? warmup);
public CustomWorkout(HKWorkoutActivityType activity, HKWorkoutSessionLocationType location, string? displayName, WorkoutStep? warmup, IEnumerable<IntervalBlock> blocks);
public CustomWorkout(HKWorkoutActivityType activity, HKWorkoutSessionLocationType location, string? displayName, WorkoutStep? warmup, IEnumerable<IntervalBlock> blocks, WorkoutStep? cooldown = null);Members: Activity, Location, DisplayName, Warmup (WorkoutStep?), Blocks (IReadOnlyList<IntervalBlock>), Cooldown (WorkoutStep?). Static helpers: SupportsActivity(activity), SupportsAlert(…), SupportsGoal(goal, activity, location = Unknown).
A workout with one goal.
public SingleGoalWorkout(HKWorkoutActivityType activity);
public SingleGoalWorkout(HKWorkoutActivityType activity, HKWorkoutSessionLocationType location);
public SingleGoalWorkout(HKWorkoutActivityType activity, HKWorkoutSessionLocationType location, HKWorkoutSwimmingLocationType swimmingLocation);
public SingleGoalWorkout(HKWorkoutActivityType activity, HKWorkoutSessionLocationType location, HKWorkoutSwimmingLocationType swimmingLocation, WorkoutGoal goal);Members: Activity, Location, SwimmingLocation, Goal. Static helpers: SupportsActivity, SupportsGoal.
Cover a distance in a target time.
public PacerWorkout(
HKWorkoutActivityType activity,
HKWorkoutSessionLocationType location,
Swift.Foundation.Measurement<Foundation.NSUnitLength> distance,
Swift.Foundation.Measurement<Foundation.NSUnitDuration> time);Members: Activity, Location, Distance, Time. Static helper: SupportsActivity.
A multisport (triathlon-style) workout built from ordered activities.
public SwimBikeRunWorkout(IEnumerable<SwimBikeRunWorkout.Activity> activities, string? displayName = null);Members: Activities (IReadOnlyList<SwimBikeRunWorkout.Activity>), DisplayName. Static helper: SupportsActivityOrdering(IEnumerable<Activity>).
SwimBikeRunWorkout.Activity is a projected enum — build with factories, read with Tag/TryGet*:
public enum CaseTag : uint { Swimming = 0, Cycling = 1, Running = 2 }
Activity.Swimming(HKWorkoutSwimmingLocationType …);
Activity.Cycling(HKWorkoutSessionLocationType …);
Activity.Running(HKWorkoutSessionLocationType …);
// TryGetSwimming/TryGetCycling/TryGetRunning(out …) to read backWorkoutPlan wraps any of the four workout types via the nested WorkoutPlan.WorkoutType projected enum:
public WorkoutPlan(WorkoutPlan.WorkoutType workout, System.Guid id);
public WorkoutPlan(byte[] data); // reconstruct from a serialized representationMembers: Workout (WorkoutPlan.WorkoutType), Id (System.Guid), DataRepresentation (byte[] — serialize a plan for storage/transfer).
WorkoutPlan.WorkoutType factories and cases:
public enum CaseTag : uint { Goal = 0, Custom = 1, Pacer = 2, SwimBikeRun = 3 }
WorkoutType.Goal(SingleGoalWorkout …);
WorkoutType.Custom(CustomWorkout …);
WorkoutType.Pacer(PacerWorkout …);
WorkoutType.SwimBikeRun(SwimBikeRunWorkout …);
// TryGetGoal/TryGetCustom/TryGetPacer/TryGetSwimBikeRun(out …) to read back
// .Activity exposes the resolved HKWorkoutActivityTypevar plan = new WorkoutPlan(WorkoutPlan.WorkoutType.Pacer(pacer), Guid.NewGuid());
// Persist / restore:
byte[] data = plan.DataRepresentation;
var roundtripped = new WorkoutPlan(data);WorkoutScheduler delivers plans to the paired Apple Watch. It is exercised end to end by the bindings.
| Member | Signature | |
|---|---|---|
WorkoutScheduler.Shared |
static property | the scheduler singleton |
WorkoutScheduler.IsSupported |
static bool |
whether scheduling is available on this device |
WorkoutScheduler.MaxAllowedScheduledWorkoutCount |
static int |
upper bound on concurrently scheduled workouts |
RequestAuthorizationAsync |
Task<WorkoutScheduler.AuthorizationStateType> RequestAuthorizationAsync(CancellationToken ct = default) |
request permission |
GetAuthorizationStateAsync |
Task<WorkoutScheduler.AuthorizationStateType> GetAuthorizationStateAsync(CancellationToken ct = default) |
current permission state |
ScheduleAsync |
Task ScheduleAsync(WorkoutPlan workout, Swift.Foundation.DateComponents at, CancellationToken ct = default) |
schedule a plan |
RemoveAsync |
Task RemoveAsync(WorkoutPlan workout, Swift.Foundation.DateComponents at, CancellationToken ct = default) |
remove one scheduled workout |
RemoveAllWorkoutsAsync |
Task RemoveAllWorkoutsAsync(CancellationToken ct = default) |
clear all |
MarkCompleteAsync |
Task MarkCompleteAsync(WorkoutPlan workout, Swift.Foundation.DateComponents at, CancellationToken ct = default) |
mark a scheduled workout done |
GetScheduledWorkoutsAsync |
Task<IReadOnlyList<ScheduledWorkoutPlan>> GetScheduledWorkoutsAsync(CancellationToken ct = default) |
list scheduled workouts |
var scheduler = WorkoutScheduler.Shared;
if (WorkoutScheduler.IsSupported)
{
var state = await scheduler.RequestAuthorizationAsync();
if (state == WorkoutScheduler.AuthorizationStateType.Authorized)
{
await scheduler.ScheduleAsync(plan, when);
IReadOnlyList<ScheduledWorkoutPlan> scheduled =
await scheduler.GetScheduledWorkoutsAsync();
}
}public enum AuthorizationStateType : long
{
NotDetermined = 0, Restricted = 1, Denied = 2, Authorized = 3
}A ScheduledWorkoutPlan pairs a plan with a date:
public ScheduledWorkoutPlan(WorkoutPlan plan, Swift.Foundation.DateComponents date);Members: Plan (WorkoutPlan), Date (Swift.Foundation.DateComponents), Complete (bool).
StateError is the scheduling failure enum: WatchNotPaired = 0, WorkoutApplicationNotInstalled = 1. The async scheduling calls are throws in Swift, so failures surface as Swift.Runtime.SwiftException.
HealthKit-backed writes are out of scope. The scheduler schedules plans, but mutating HealthKit data models through this binding is not supported in this SDK version. Scheduling only delivers when a paired Apple Watch with your companion app is present.
Generated types implement ISwiftObject / IDisposable. For short-lived locals the finalizer cleans up, but using var is the recommended pattern for deterministic cleanup — Dispose is safe on every generated type and double-Dispose is a no-op.
using var workout = new SingleGoalWorkout(runningActivity); // runningActivity: HealthKit.HKWorkoutActivityType-
WorkoutGoal.Openis a cached singleton — don't wrap it inusing(Dispose on it is a no-op, but treat it as shared). -
Several struct wrappers are
Sendablein Swift (ScheduledWorkoutPlan,WorkoutGoal, …) — the binding marks them[SwiftSendable], so instances may be shared across .NET threads without external locking. -
Async + cancellation. All scheduler operations are
*Asyncwith a trailingCancellationToken; await off the UI thread and marshal back before touching UI. -
Platform availability. Many WorkoutKit types carry
[SupportedOSPlatform]annotations (iOS 17+, macOS 15+, watchOS 10+). The compiler will flag a call that isn't valid for your target — check IntelliSense availability hints.
-
ActivityKit —
SwiftBindings.Apple.ActivityKitv26.2.6 -
CryptoKit —
SwiftBindings.Apple.CryptoKitv26.2.6 -
FamilyControls —
SwiftBindings.Apple.FamilyControlsv26.2.6 -
LiveCommunicationKit —
SwiftBindings.Apple.LiveCommunicationKitv26.2.6 -
Matter —
SwiftBindings.Apple.Matterv26.2.6 -
MatterSupport —
SwiftBindings.Apple.MatterSupportv26.2.6 -
MusicKit —
SwiftBindings.Apple.MusicKitv26.2.6 -
ProximityReader —
SwiftBindings.Apple.ProximityReaderv26.2.6 -
RealityFoundation —
SwiftBindings.Apple.RealityFoundationv26.2.6 -
RealityKit —
SwiftBindings.Apple.RealityKitv26.2.6 -
RoomPlan —
SwiftBindings.Apple.RoomPlanv26.2.6 -
StoreKit2 —
SwiftBindings.Apple.StoreKit2v26.2.6 -
TipKit —
SwiftBindings.Apple.TipKitv26.2.6 -
Translation —
SwiftBindings.Apple.Translationv26.2.6 -
WeatherKit —
SwiftBindings.Apple.WeatherKitv26.2.6 -
WorkoutKit —
SwiftBindings.Apple.WorkoutKitv26.2.6