A minimal, production-ready iOS bootstrap for building vision-based fitness apps.
FormKit wires together Apple's Vision framework, AVFoundation, and SwiftUI so you can go from zero to a working real-time pose-detection app in minutes β without boilerplate. Clone it, drop in your exercise logic, and ship.
| Feature | Details |
|---|---|
| π· Live camera feed | Front/back switchable AVCaptureSession with portrait orientation and mirroring |
| 𦴠Real-time skeleton overlay | Glowing stick-figure drawn on top of the camera preview using SwiftUI Path |
| π€Έ Human body pose detection | Apple Vision DetectHumanBodyPoseRequest β 15 joints tracked at up to 30 fps |
| π Rep counter engine | Protocol-based ExerciseRule β implement one method to count any exercise |
| π Live debug HUD | On-screen FPS counter + joint coordinates overlay for rapid iteration |
| β‘ Efficient frame processing | Processes every 3rd frame, drops late frames, avoids redundant work |
| π Thread-safe architecture | @MainActor isolation for UI, dedicated session & processing queues for capture |
Real-time pose skeleton rendered over the front camera feed.
WIll be added later.
FormKit/
βββ FormKitApp.swift # App entry point
βββ ContentView.swift # Root view β PoseDetectionView
β
βββ Camera/
β βββ CameraViewModel.swift # AVCaptureSession lifecycle, camera switching
β βββ CameraView.swift # SwiftUI session-state router (loading/error/running)
β βββ CameraPreview.swift # UIViewRepresentable wrapping AVCaptureVideoPreviewLayer
β βββ CameraViewWrapper.swift # Convenience wrapper (session start/stop on appear)
β βββ PoseEstimator.swift # @MainActor Vision inference engine + FPS tracking
β βββ ExerciseRepCounter.swift # Protocol-driven rep-counting engine
β βββ CIImage_Extension.swift # CIImage β CGImage helper
β βββ CMSampleBuffer_Extension.swift # CMSampleBuffer β CGImage helper
β
βββ Skeleton/
β βββ FreePostureStickFigureView.swift # SwiftUI skeleton overlay (bones + joints)
β βββ Stick.swift # Generic polyline Shape (coordinate flip helper)
β
βββ Views/
βββ PoseDetectionView.swift # Main screen: camera + skeleton + debug HUD
AVCaptureSession
β (sample buffer, every 3rd frame)
βΌ
PoseEstimator β nonisolated AVCaptureVideoDataOutputSampleBufferDelegate
β DetectHumanBodyPoseRequest (Vision)
β publishes bodyParts [@MainActor]
βΌ
ExerciseRepCounter β called via $bodyParts Combine subscription
β ExerciseRule.evaluate(joints:)
β publishes currentReps, currentPhase
βΌ
PoseDetectionView β @StateObject, redraws on each publish
βββ CameraView (live preview)
βββ FreePostureStickFigureView (skeleton)
βββ debugOverlay (HUD)
- Xcode 16+
- iOS 17+ (uses
DetectHumanBodyPoseRequestfrom the Vision v2 API) - A physical iPhone or iPad β the Simulator does not provide a camera feed
git clone https://github.com/LachPawel/FormKit.git
cd FormKit
open FormKit.xcodeprojSelect your device in Xcode and press βR.
Camera permission β Xcode will prompt automatically on first launch. Make sure
NSCameraUsageDescriptionis set inInfo.plist(already included).
All exercise intelligence lives in one place: ExerciseRepCounter.swift.
struct BicepCurlRule: ExerciseRule {
let name = "Bicep Curl"
private var phase: Phase = .down
private enum Phase { case up, down }
mutating func evaluate(
joints: [HumanBodyPoseObservation.PoseJointName: Joint],
currentRepCount: Int
) -> RepCounterUpdate {
guard
let shoulder = joints[.rightShoulder],
let elbow = joints[.rightElbow],
let wrist = joints[.rightWrist],
shoulder.confidence > 0.5,
elbow.confidence > 0.5,
wrist.confidence > 0.5
else {
return RepCounterUpdate(didIncrementRep: false, phase: "tracking lost", debugMessage: "low confidence")
}
let angle = elbowAngle(shoulder: shoulder.location,
elbow: elbow.location,
wrist: wrist.location)
switch phase {
case .down where angle < 50:
phase = .up
return RepCounterUpdate(didIncrementRep: false, phase: "up", debugMessage: "angle=\(Int(angle))Β°")
case .up where angle > 150:
phase = .down
return RepCounterUpdate(didIncrementRep: true, phase: "down", debugMessage: "rep! angle=\(Int(angle))Β°")
default:
return RepCounterUpdate(didIncrementRep: false, phase: phase == .up ? "up" : "down", debugMessage: "angle=\(Int(angle))Β°")
}
}
mutating func reset() { phase = .down }
}In PoseDetectionView.swift (or wherever you initialise PoseEstimator), pass your rule to the counter:
// PoseEstimator.swift β inside init()
repCounter = ExerciseRepCounter(rule: BicepCurlRule())That's it. repCounter.currentReps and repCounter.currentPhase are already @Published and available in every view that observes PoseEstimator.
@MainActor class PoseEstimator: ObservableObject
- Conforms to
AVCaptureVideoDataOutputSampleBufferDelegatevia anonisolatedextension β safe to call from any queue - Processes every 3rd frame to balance accuracy and battery life
- Calculates live FPS using
CACurrentMediaTime - Filters joints by
confidence > 0before publishing
class CameraViewModel: ObservableObject
- Manages
AVCaptureSessionon a dedicated serialsessionQueue - Supports front β back camera switching at runtime via
NotificationCenter(.switchCamera) - Disables the idle timer while the session is active to prevent screen sleep
- Exposes
sessionState: CameraSessionState(.initializing/.running/.failed) for UI feedback
- Draws bones as
Pathstrokes with aLinearGradientand a glowshadow - Draws joints as layered
Circleshapes (glow + white fill + accent border) - Low-confidence joints (
< 0.5) show a red debug ring - Coordinate conversion: Vision's
(0,0)is bottom-left; the view flips Y to match UIKit
protocol ExerciseRule {
var name: String { get }
mutating func evaluate(joints: [...], currentRepCount: Int) -> RepCounterUpdate
mutating func reset()
}A value type (struct) is preferred so phase state is contained inside the rule.
Apple Vision provides 19 named joints. FormKit uses the following subset:
All joint names match HumanBodyPoseObservation.PoseJointName from the Vision framework.
Add the following key to your Info.plist:
<key>NSCameraUsageDescription</key>
<string>FormKit uses the camera to detect your body pose in real time.</string>No camera data is stored or transmitted. All inference runs on-device using Apple Vision.
- Fork the repo and create a feature branch:
git checkout -b feature/my-exercise - Add your
ExerciseRuleimplementation (and tests if applicable) - Open a pull request β please include a short screen recording if the change is visual
MIT β see LICENSE for details.
- Apple Vision Framework β on-device human body pose detection
- Apple AVFoundation β camera capture pipeline
- Detecting Human Body Poses in Images β the capability to detect human body poses to your app using the Vision framework.