Looking to run the iOS app on Simulator? Start here: README.md.
The main entry point for the BetterFit library.
public class BetterFitplanManager: PlanManager- Manages training planstemplateManager: TemplateManager- Manages workout templatesequipmentSwapManager: EquipmentSwapManager- Handles equipment swapsbodyMapManager: BodyMapManager- Tracks recovery statussocialManager: SocialManager- Manages social featuresnotificationManager: SmartNotificationManager- Handles notificationsautoTrackingService: AutoTrackingService- Processes Watch sensor dataaiAdaptationService: AIAdaptationService- AI-powered plan adaptationimageService: EquipmentImageService- Equipment image management
public func startWorkout(_ workout: Workout)Starts a workout and enables auto-tracking.
public func completeWorkout(_ workout: Workout)Completes a workout, updating history, recovery, streaks, and AI analysis.
public func getWorkoutHistory() -> [Workout]Returns all completed workouts.
public func getRecommendedWorkout() -> Workout?Gets a recommended workout based on current plan and recovery status.
public func processMotionData(_ data: MotionData) -> TrackingEvent?Processes Apple Watch motion data for auto-tracking.
public func getTrackingStatus() -> TrackingStatusReturns the current auto-tracking status.
HealthExportParser.parseExport(data:) and parseExport(xml:) return a
HealthExport containing workouts, records, and activitySummaries.
The parser uses Foundation and does not require HealthKit or UI frameworks.
Each HealthActivitySummary preserves the exported dateComponents day
string (yyyy-MM-dd), active energy and goal with their original unit,
exercise time and goal in minutes, and stand hours and goal. Missing or
invalid numeric attributes are nil; measured zero remains zero.
activeEnergyKilocalories and activeEnergyGoalKilocalories convert kcal,
kJ, and J to kilocalories, returning nil for missing or unsupported units.
Activity summaries remain separate from dailySummaries(calendar:), which
aggregates records. Adding the two energy totals would count the same
activity twice. Older encoded exports without activitySummaries decode
with an empty collection.
[HealthActivitySummary] supports csvString(), jsonData(), writeCSV(to:)
and writeJSON(to:). Exports preserve array order and duplicate days. CSV
columns are date, active_energy_kcal, active_energy_goal_kcal,
exercise_minutes, exercise_goal_minutes, stand_hours and stand_goal_hours.
The date stays as the exported calendar day, without time zone conversion.
Missing values and energy with unsupported units are empty CSV fields;
measured zero remains zero. Text containing commas, quotes or line breaks is
quoted, with embedded quotes doubled. Empty CSV exports contain the header
and a newline.
JSON retains the original energy values and unit, uses sorted keys and
omits missing fields. Decode it with a standard JSONDecoder to restore the
same summaries. Empty JSON exports contain an empty array. jsonData() throws
on encoding errors, including non-finite numbers. File writers atomically
create or replace their destination and propagate encoding or file errors;
the parent directory must already exist.
let export = try HealthExportParser.parseExport(data: xmlData)
try export.activitySummaries.writeCSV(to: csvURL)
try export.activitySummaries.writeJSON(to: jsonURL)
let restored = try JSONDecoder().decode(
[HealthActivitySummary].self, from: export.activitySummaries.jsonData())Represents a single exercise.
public struct Exercise: Identifiable, Codable, Equatableid: UUIDname: StringequipmentRequired: EquipmentmuscleGroups: [MuscleGroup]imageURL: String?
Equipment types for exercises.
public enum Equipment: String, Codable, CaseIterablebarbell,dumbbell,kettlebell,machine,cable,bodyweight,bands,other
alternatives() -> [Equipment]- Returns alternative equipment options
Muscle groups targeted by exercises.
public enum MuscleGroup: String, Codable, CaseIterablechest,back,shoulders,biceps,triceps,forearmsabs,obliques,quads,hamstrings,glutes,calves,traps,lats
bodyMapRegion: String- Body map region for recovery tracking
Represents a single set in an exercise.
public struct ExerciseSet: Identifiable, Codable, Equatableid: UUIDreps: Intweight: Double?isCompleted: Booltimestamp: Date?autoTracked: Bool
Represents a workout session.
public struct Workout: Identifiable, Codable, Equatableid: UUIDname: Stringexercises: [WorkoutExercise]date: Dateduration: TimeInterval?isCompleted: BooltemplateId: UUID?
Reusable workout template.
public struct WorkoutTemplate: Identifiable, Codable, Equatableid: UUIDname: Stringdescription: String?exercises: [TemplateExercise]tags: [String]createdDate: DatelastUsedDate: Date?
createWorkout() -> Workout- Converts template to a workout
Training plan for structured programming.
public struct TrainingPlan: Identifiable, Codable, Equatableid: UUIDname: Stringdescription: String?weeks: [TrainingWeek]currentWeek: Intgoal: TrainingGoalcreatedDate: DateaiAdapted: Bool
getCurrentWeek() -> TrainingWeek?advanceWeek()
Training goal types.
public enum TrainingGoal: String, Codable, CaseIterablestrength,hypertrophy,endurance,powerlifting,generalFitness,weightLoss
repRange: ClosedRange<Int>- Recommended rep rangerestTime: TimeInterval- Recommended rest time
Body map for tracking recovery.
public struct BodyMapRecovery: Codable, Equatableregions: [BodyRegion: RecoveryStatus]lastUpdated: Date
recordWorkout(_:)- Update each targeted body region once per recorded workout. Multiple muscles or exercises in the same region share one fatigue transition; separate recorded workouts can increase fatigue further.updateRecovery()- Update based on time elapsed
Recovery status for muscle groups.
public enum RecoveryStatus: String, Codable, Equatablerecovered,slightlyFatigued,fatigued,sore
recommendedRestHours: Double
User profile for social features.
public struct UserProfile: Identifiable, Codable, Equatableid: UUIDusername: StringcurrentStreak: IntlongestStreak: InttotalWorkouts: IntactiveChallenges: [UUID]
Workout challenge.
public struct Challenge: Identifiable, Codable, Equatableid: UUIDname: Stringdescription: Stringgoal: ChallengeGoalstartDate: DateendDate: Dateparticipants: [UUID]progress: [UUID: Double]
Challenge goal types.
public enum ChallengeGoal: Codable, EquatableworkoutCount(target: Int)totalVolume(target: Double)consecutiveDays(target: Int)specificExercise(exerciseId: UUID, target: Int)
Manages training plans.
public class PlanManagergetActivePlan() -> TrainingPlan?setActivePlan(_:)addPlan(_:)updatePlan(_:)removePlan(_:)getAllPlans() -> [TrainingPlan]
Manages workout templates.
public class TemplateManagergetAllTemplates() -> [WorkoutTemplate]getTemplate(id:) -> WorkoutTemplate?addTemplate(_:)updateTemplate(_:)deleteTemplate(id:)searchByTag(_:) -> [WorkoutTemplate]searchByName(_:) -> [WorkoutTemplate]getRecentTemplates(limit:) -> [WorkoutTemplate]createWorkout(from:) -> Workout?createTemplate(from:name:tags:) -> WorkoutTemplate
Manages equipment swaps.
public class EquipmentSwapManagersetAvailableEquipment(_:)isAvailable(_:) -> BoolfindAlternatives(for:) -> [Exercise]suggestSwaps(for:) -> [(original: Exercise, alternatives: [Exercise])]applySwap(workout:originalExerciseId:newExercise:) -> Bool
Manages body map recovery.
public class BodyMapManagergetRecoveryMap() -> BodyMapRecoveryrecordWorkout(_:)getRecoveryStatus(for:) -> RecoveryStatusisReadyForTraining(region:) -> BoolgetRecommendedExercises(available:avoidSoreRegions:) -> [Exercise]getOverallRecoveryPercentage() -> Doublereset()
Manages social features.
public class SocialManagerrecordWorkout(date:)getCurrentStreak() -> IntgetLongestStreak() -> IntgetAllChallenges() -> [Challenge]getActiveChallenges() -> [Challenge]joinChallenge(_:) -> BoolleaveChallenge(_:) -> BoolcreateChallenge(_:)updateChallengeProgress(challengeId:userId:progress:) -> BoolgetChallengeLeaderboard(challengeId:) -> [(userId: UUID, progress: Double)]checkChallengeCompletion(challengeId:userId:) -> BoolgetUserProfile() -> UserProfileupdateUserProfile(_:)
Manages smart notifications.
public class SmartNotificationManagerscheduleNotifications(userProfile:workoutHistory:activePlan:)getScheduledNotifications() -> [SmartNotification]cancelNotification(id:)cancelAllNotifications()
HealthExportParser.parseExport(xml:) reads Apple Health export XML without
requiring HealthKit. Call dailySummaries(calendar:) on the result to aggregate
records by their start date in the supplied calendar.
DailyHealthSummary.sleepDuration reports seconds asleep for legacy asleep,
unspecified, core, deep and REM categories, including their numeric HealthKit
values. Overlapping asleep intervals count once, including duplicates from
different sources. Awake, in-bed, unknown categories and incomplete or
non-positive intervals are excluded. A day without valid asleep intervals has
nil sleep duration, which remains an empty field in CSV and is omitted in JSON.
Apple documents the sleep categories and overlapping samples in
HKCategoryValueSleepAnalysis.
Intervals crossing midnight remain entirely on their start day. The summary does not prioritise sources or subtract awake samples that conflict with asleep samples. CSV and JSON exports use the same corrected duration.
Auto-tracking service for Watch sensors.
public class AutoTrackingServicestartTracking(workout:)stopTracking()processMotionData(_:) -> TrackingEvent?completeCurrentSet() -> ExerciseSet?nextExercise()getTrackingStatus() -> TrackingStatus
Motion data from Watch sensors.
public struct MotionDataacceleration: [Double]rotation: [Double]heartRate: Double?timestamp: Date
public init(
acceleration: [Double],
rotation: [Double],
heartRate: Double? = nil,
timestamp: Date = Date()
)isRepetitionDetected() -> BoolisRestPeriod() -> Bool
Tracking events from auto-tracking.
public enum TrackingEventrepDetected(count: Int)setCompleted(reps: Int)exerciseCompleted
Current auto-tracking status.
public struct TrackingStatusisTracking: BoolcurrentExercise: IntdetectedReps: Int
AI service for adaptive training.
public class AIAdaptationServiceanalyzePerformance(workouts:currentPlan:) -> [Adaptation]applyAdaptations(_:to:)
Training plan adaptation suggestions.
public enum Adaptation: EquatablereduceVolume(percentage: Int)increaseVolume(percentage: Int)adjustIntensity(change: Int)deloadWeek
Service for equipment images.
public class EquipmentImageServicegetImage(for: Equipment) -> EquipmentImage?getImage(for: Exercise) -> EquipmentImage?cacheImage(_:for:)getAllImages() -> [EquipmentImage]loadCustomImage(url:for:) async throws -> EquipmentImagegenerateAIImage(for:style:) async throws -> EquipmentImage
Equipment image model.
public struct EquipmentImage: Identifiable, Equatableid: UUIDequipmentType: Equipmenturl: Stringis3D: BoolisAIGenerated: Bool
Image generation styles.
public enum ImageStyle: String, CaseIterablerealistic3D,cartoon,schematic,photographic
Parses an Apple Health export (export.xml). Foundation only, so it runs off device and in tests.
public enum HealthExportParserparseWorkouts(data:)/parseWorkouts(xml:)- workouts with child statisticsparseRecords(data:)/parseRecords(xml:)- top-level records (steps, heart rate, energy, distance, body mass, sleep)parseExport(data:)/parseExport(xml:)- both of the above as oneHealthExport
Throws HealthExportParser.ParseError.malformedXML on malformed XML.
A workout from the export, with one WorkoutStatistics per quantity type nested under it. ParsedWorkout exposes per-workout summaries: activeEnergyKilocalories, basalEnergyKilocalories, distanceMetres, averageHeartRate, minimumHeartRate, maximumHeartRate, and stepCount. Statistics win over the totals written on the workout element, and unknown statistic types are kept as .other(type).
A top-level record from the export. Quantity records carry a numeric value. Category records, such as sleep analysis, carry categoryValue as a string instead. Normalised helpers: valueKilocalories, valueMetres, valueKilograms, and duration from the start and end dates. Unknown record types are kept as .other(type).
HealthExport holds workouts and records together, with records(of:) to filter records by kind.
HealthExport.weeklyTrainingSummaries(calendar:) groups workouts by their start
date into calendar weeks and returns summaries in date order. Workouts without
a start date are skipped. Pass an explicit calendar to control the time zone
and first day of the week.
[WeeklyTrainingSummary] supports csvString(), jsonData(), writeCSV(to:)
and writeJSON(to:). All four methods throw on encoding or file errors. Exports
preserve array order and use ISO8601 dates. File writes atomically create or
replace the destination, whose parent directory must already exist.
CSV columns are week_start, workout_count, duration_seconds,
active_energy_kcal, distance_metres, average_heart_rate,
maximum_heart_rate and activity_counts. Heart rates are in beats per minute;
the average is the unweighted mean of per-workout averages. Missing metrics
are empty CSV fields and omitted JSON properties; measured zero stays zero.
Activity counts use a JSON object with sorted keys inside a quoted CSV field,
so activity names containing commas or quotes are preserved. Empty CSV exports
contain only the header and a newline; empty JSON exports contain an empty array.
let export = try HealthExportParser.parseExport(data: xmlData)
let weeks = export.weeklyTrainingSummaries(calendar: calendar)
try weeks.writeCSV(to: csvURL)
try weeks.writeJSON(to: jsonURL)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let restored = try decoder.decode([WeeklyTrainingSummary].self, from: weeks.jsonData())