BikeControl (SwiftControl)
Zwift Click과 Shimano 컨트롤러를 MyWhoosh와 Rouvy와 Zwift에서 쓴다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Strength training for iPhone and Apple Watch, synced with WatchConnectivity.
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Small actions. Repeated daily.
Axis is a strength-training app for iPhone and Apple Watch. Build workout routines on your iPhone, run them from your wrist set by set, and see every finished session in your history on the phone. The two apps stay in sync through WatchConnectivity.
Case study: How Axis is designed and built on my portfolio, covering the architecture, code decisions and trade-offs.
Built with SwiftUI, SwiftData, and WatchConnectivity.
iPhone Apple Watch
┌──────────────────────────┐ ┌──────────────────────────┐
│ Create / edit routines │ ── templates ──► │ Pick a routine │
│ (SwiftData) │ application │ Track reps & sets │
│ │ context │ Pause / resume timer │
│ History of completed │ ◄── workout ──── │ Session complete │
│ workouts │ sendMessage / │ │
│ │ transferUserInfo│ │
└──────────────────────────┘ └──────────────────────────┘
Both apps switch between a day theme (7am–7pm) and a night theme automatically. There's no toggle; a timer checks the clock every minute and again whenever the app comes to the foreground.
Theme/AccessibilityModifiers.swiftWorkoutTemplate ──► [TemplateExercise] ──► Exercise (the plan)
CompletedWorkout ──► [CompletedExercise] (what you actually did)
Exercise: an entry in the exercise library (name, equipment, whether it's custom)TemplateExercise: an exercise placed in a routine, with its order and target sets and repsWorkoutTemplate: a named routineCompletedWorkout / CompletedExercise: a snapshot of a finished session. It's stored separately so editing a routine later never changes past history.Only small, lightweight Codable payloads cross the connection. SwiftData models never leave the phone.
| Direction | Payload | Transport | Why |
|---|---|---|---|
| iPhone → Watch | [WatchTemplatePayload] | updateApplicationContext | Only the latest set of routines matters, and the Watch gets it the next time it wakes |
| Watch → iPhone | WatchCompletedWorkoutPayload | sendMessage, falling back to transferUserInfo | Delivered immediately when the phone is reachable; otherwise queued so a finished workout is never lost |
On the phone, PhoneConnectivityManager publishes the incoming workout, and RoutinesHome turns it into a CompletedWorkout and saves it.
WatchWorkoutSession (@Observable) holds all of the in-workout state: rep counting, set completion, moving to the next exercise, the pause and resume clock, and building the payload. The views stay thin, and the logic can be tested without any UI.
Axis/ # iPhone app
├── App/ # AxisApp, RootView (onboarding flow), AppState
├── Core/
│ ├── Models/ # SwiftData models
│ └── Services/ # PhoneConnectivityManager, Watch payloads
├── Features/
│ ├── Launch/ Onboarding/
│ ├── Routines/ # Home, CreateRoutine, RoutineEditor, exercise library
│ └── History/
└── Theme/ # Colors, typography, button style, ThemeManager
AxisWatch Watch App/ # watchOS app
├── Services/ # WatchConnectivityManager, WatchWorkoutSession, theme
└── Views/ # Greeting, WorkoutSelector, StartedWorkout, WorkoutComplete
AxisTests/ # iPhone unit tests
AxisWatch Watch AppTests/ # Watch unit tests
Both targets use Swift Testing (@Suite / @Test), with about 100 tests in total:
CompletedWorkout, the template and completed-exercise models, EquipmentType, and RoutineEditingLogic (exercise filtering and save validation)WatchWorkoutSession from start to finish (rep tracking, completing sets, moving between exercises, the last-exercise check, pausing and resuming, building the payload) and WatchActiveExerciseRun them in Xcode with ⌘U, choosing the iPhone or Watch scheme.
git clone https://github.com/arturoa925/Axis.git
cd Axis
open Axis.xcodeproj
Axis and AxisWatch Watch App targets, and change the bundle IDs (AA.Axis, AA.Axis.watchkitapp) to your own.CompletedWorkout already has totalCalories and averageHeartRate fields ready for thisExercise.isCustom is already in the model)