Back to work
Personal · Native iOS app · 2026

Caribou

"Exercise to earn screen-time." A native iOS app that detects push-ups, squats and jumping jacks through the camera (on-device, Vision Framework) and unlocks your blocked apps via the Screen Time API.

Swift 6 SwiftUI iOS 17+ Vision Framework Screen Time API StoreKit 2 Firebase XcodeGen
Challenge

Break compulsive scrolling without a punitive blocker — find the carrot, not the stick.

Approach

On-device exercise detection via Vision Framework (native iOS). 10 push-ups = 10 minutes of scroll unlocked. StoreKit 2 for freemium, Screen Time API for app blocking.

Outcome

Native iOS app live on TestFlight.

01Context

Useful friction

Scrolling has become too easy — the upward finger swipe costs nothing, not even a decision. Caribou puts a price back on scrolling: a measurable physical effort. No vague promises — one jumping jack equals one coin, ten coins equal five minutes of Instagram.

Gamification is intentional. You build streaks, challenge friends (50 push-ups, first to finish wins), unlock badges. The goal isn't to become athletic. It's to buy your screen-time knowingly.

02What I built

Native iOS app, on-device

  • On-device exercise detection via Apple Vision Framework (VNDetectHumanBodyPoseRequest) — no image ever leaves the phone.
  • PoseAnalyzer — joint-angle math (elbow, hip, knee) to validate a rep, with debouncing to avoid false positives.
  • App blocking via Screen Time API (FamilyControls + ManagedSettings) + a custom ShieldActionExtension showing "earn coins to unlock".
  • Social layer — asynchronous friend challenges via Firestore, push notifications when a friend beats your score.
  • Freemium — free tier: 2 blocked apps, 3 exercises. Premium (StoreKit 2): unlimited apps, advanced challenges, detailed stats.
03Architecture

Swift 6 strict concurrency, MVVM

Each feature (Onboarding, Home, Workout, AppBlocking, Social, Profile, Stats) has SwiftUI views + a @MainActor view model. Services (ExerciseDetector, FirestoreService, ScreenTimeManager, CoinManager, SubscriptionService…) are injected via @EnvironmentObject from CaribouApp.swift.

The Xcode project is generated from a project.yml file via XcodeGen — no .xcodeproj in the repo, everything rebuilds with one command.

04Stack
Swift 6Strict concurrency
SwiftUIDeclarative UI, iOS 17+
Vision FrameworkOn-device pose detection
Screen Time APISystem-level app blocking
StoreKit 2In-app subscriptions
FirebaseAuth, Firestore, FCM, Functions
XcodeGenXcode project from YAML
TypeScriptCloud Functions backend