A privacy-first workout tracking app for iOS — No accounts, no tracking, completely offline
WorkOut Log is a modern iOS workout tracking application built with SwiftUI, SwiftData, and Clean Architecture. Track your exercises, sets, and training volume with complete privacy — all data stays on your device, no internet required.
- 🔒 Privacy First: Zero data collection, no analytics, no third-party SDKs
- 📴 Fully Offline: Works without internet connection
- 🎨 Native iOS: Built with latest SwiftUI, Swift 6, and iOS 18 APIs
- 🏗️ Clean Code: Follows Clean Architecture principles with comprehensive tests
- Create Sessions: Quick session creation with date selection
- Add Sets: Record exercise name, weight (kg), and reps
- Auto-Calculated Volume: Automatic volume calculation (weight × reps)
- Clone Workouts: Duplicate recent sessions to save time
- 7 Categories: Lower Body, Upper Body, Cardio, Core, Full Body, Arms, Shoulders
- Custom Exercises: Add your own exercises with custom names
- Search & Filter: Fast search with category filtering
- Delete Management: Remove unused exercises
- Daily Trends: View last 10 days of training volume with two-line axis labels (month/day)
- Weekly Trends: Sunday-Saturday weekly buckets (last 8 weeks) with day-range labels
- Monthly Trends: Monthly volume aggregation (last 6 months)
- Category Filtering: Filter trends by muscle group
- Interactive Charts: Built with Swift Charts for smooth, native performance
- PR Tracking: Automatic personal record detection
- Epley 1RM Formula: Estimated one-rep max calculation
- Recent PR Highlights: See your latest achievements
- VoiceOver Support: Full screen reader accessibility with custom labels
- Dynamic Type: Respects user text size preferences
- High Contrast: Readable in all lighting conditions
- Accessibility Identifiers: Complete UI testing coverage
|
Session List View all your workouts |
Session Detail Track sets and exercises |
Trends (Weekly) Sunday-Saturday aggregation |
|
Trends (Daily) Last 10 days with Show All Days option |
Exercise Search Find exercises by name or category |
Splash Screen App branding and logo |
Note: Screenshots are available in AppStore metadata for submission. See /AppStore/ directory.
This app follows Clean Architecture principles with clear separation of concerns:
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Presentation │───▶│ Domain │◀───│ Data │
│ │ │ │ │ │
│ • Views │ │ • Entities │ │ • Repositories │
│ • ViewModels │ │ • Use Cases │ │ • SwiftData │
│ • @Observable │ │ • Protocols │ │ • Mappers │
└─────────────────┘ └──────────────┘ └─────────────────┘
Presentation Layer
- SwiftUI views and navigation
@ObservableViewModels (Observation framework)- User interaction handling
- UI state management
Domain Layer (Pure Swift — NO SwiftUI/SwiftData imports)
- Business entities (Exercise, Session, SetRecord)
- Use cases (UpsertExercise, CreateSession, ComputeVolumeTrend)
- Repository protocols
- Business logic and validation
Data Layer
- SwiftData
@Modelclasses - Repository implementations
- Domain ↔ Data mappers
- Persistence layer (SwiftData ModelContext)
📄 Architecture Decision Records (ADRs):
- ADR-001: SwiftData Adoption
- ADR-002: Clean Architecture
- ADR-003: Sunday-Saturday Week Buckets (updated from ISO to Sunday-start)
- ADR-004: Personal Record Definition
WorkoutSession
struct WorkoutSession {
let id: String
let date: Date
let note: String?
}SetRecord
struct SetRecord {
let id: String
let sessionID: String
let exerciseID: String
let weight: Double
let reps: Int
let order: Int
// Computed: volume = weight × reps
}Exercise
struct Exercise {
let id: String
let name: String
let main: ExerciseCategoryMain
// Categories: lowerBody, upperBody, cardio, core, fullBody, arms, shoulders
}Session (1) ─────────── (N) SetRecord
│
│ exerciseID
▼
Exercise (1)
- No KeyPath chaining to value-type members (e.g.,
\.name.localizedLowercase) FetchDescriptor.fetchLimitis a property, not a constructor parameter#Predicate<Model>requires generic type annotation- Use
SortDescriptor(\Model.property, order: .forward)for sorting
- Xcode 16+ (with full Xcode.app, not just Command Line Tools)
- iOS 18.0+ Simulator or physical device
- macOS 15+ (Sequoia)
- Swift 6 language mode enabled
# Clone repository
git clone https://github.com/jungseok-corine/WorkOut_Log.git
cd "WorkOut Log"
# Open in Xcode
open "WorkOut Log.xcodeproj"
# Or build from command line
xcodebuild -scheme workout_log \
-destination 'platform=iOS Simulator,name=iPhone 16' \
clean build# Run all unit and domain tests
xcodebuild -scheme workout_log \
-destination 'platform=iOS Simulator,name=iPhone 16' \
test
# Run specific test class
xcodebuild -scheme workout_log \
-destination 'platform=iOS Simulator,name=iPhone 16' \
test -only-testing:workout_logTests/WeeklyBucketTests
# Enable code coverage
xcodebuild -scheme workout_log \
-destination 'platform=iOS Simulator,name=iPhone 16' \
test -enableCodeCoverage YESWorkOut Log/
├── App/
│ ├── DI/AppContainer.swift # Dependency injection
│ └── WorkOut_LogApp.swift # App entry point
│
├── Presentation/
│ └── Scenes/
│ ├── SessionList/ # Session overview & list
│ ├── SessionDetail/ # Set management & editing
│ ├── Exercise/ # Exercise search & picker
│ ├── Trends/ # Volume trend charts
│ └── Splash/ # App splash screen
│
├── Domain/ # ⚠️ NO SwiftUI/SwiftData imports
│ ├── Entities/ # Pure Swift structs
│ ├── UseCases/ # Business logic
│ └── Repositories/ # Protocol definitions only
│
├── Data/
│ ├── SwiftDataModels/ # @Model classes
│ ├── Repositories/ # Repository implementations
│ └── Mappers/ # Domain ↔ Data conversion
│
└── Tests/
├── DomainTests/ # Unit tests (Domain layer)
├── DataTests/ # Integration tests (Data layer)
└── UITests/ # UI automation tests
- Domain Layer: 85%+ coverage (use cases, entities, business logic)
- Data Layer: 75%+ coverage (repositories, SwiftData queries)
- Presentation Layer: UI tests for critical workflows
WeeklyBucketTests.swift: Verifies Sunday-Saturday week aggregationDailyTrendAxisTests.swift: Validates ≤10 points cap and orderingExerciseRepositorySearchTests.swift: Tests client-side filteringSessionListViewModelTests.swift: ViewModel state management
# Test weekly bucket aggregation
xcodebuild test -only-testing:workout_logTests/WeeklyBucketTests
# Test daily trend axis
xcodebuild test -only-testing:workout_logTests/DailyTrendAxisTests
# Test exercise search
xcodebuild test -only-testing:workout_logTests/ExerciseRepositorySearchTests- Custom Labels: All charts and controls have descriptive accessibility labels
- Trends Chart: "Daily training volume trend" / "Weekly training volume trend"
- Axis Labels: Combined labels for two-line axis marks (e.g., "October 9th" instead of separate "Oct" and "9")
- Category Chips: Identified as
categoryChip_all,categoryChip_lowerBody, etc.
- All text scales with user's preferred reading size
- Minimum touch target sizes (44×44pt) maintained
- Readable at all Dynamic Type sizes (XS to XXXL)
- Sufficient color contrast ratios (WCAG AA compliant)
- Dark mode support with semantic colors
- Focus indicators for keyboard navigation
# Run accessibility audit in Xcode
# 1. Product → Analyze
# 2. Check "Accessibility" warnings
# Test VoiceOver manually
# 1. Simulator → Accessibility Inspector
# 2. Enable VoiceOver (Cmd+F5)
# 3. Navigate with Tab/Shift+Tab- ✅ No Analytics: No Firebase, Mixpanel, or tracking SDKs
- ✅ No Crash Reporting: No Sentry, Crashlytics, or remote logging
- ✅ No Network Requests: 100% offline functionality
- ✅ No Third-Party SDKs: Pure Apple frameworks only
- Local Only: All data stored on-device via SwiftData
- iCloud Backup: Optional (user-controlled via iOS Settings)
- No Cloud Sync: No custom cloud synchronization
Full privacy policy available at:
- File: AppStore/PrivacyPolicy.md
- Web: https://github.com/jungseok-corine/WorkOut_Log/blob/main/AppStore/PrivacyPolicy.md
Summary: We collect zero data. Your workouts stay on your device.
✅ Ready for submission — All metadata, screenshots, and documentation prepared.
See AppStore/SubmissionChecklist.md for complete guide.
Quick submission:
# 1. Build and test
xcodebuild -scheme workout_log -destination 'platform=iOS Simulator,name=iPhone 16' clean build test
# 2. Archive for distribution (in Xcode)
# Product → Archive → Distribute App → App Store Connect
# 3. Submit via App Store Connect
# https://appstoreconnect.apple.comAll App Store metadata ready in /AppStore/ directory:
- English (
Description.en-US.md,Keywords.en-US.txt, etc.) - Korean (
Description.ko-KR.md,Keywords.ko-KR.txt, etc.) - Privacy policy (
PrivacyPolicy.md) - Review notes (
ReviewNotes.md)
- Developer Guide: Comprehensive architecture guide
- Tutorial (HTML): Printable beginner tutorial (A4-ready PDF export)
- ADRs: Architecture decision records in
/ADR/directory
- Privacy Policy: AppStore/PrivacyPolicy.md
- Release Notes: AppStore/ReleaseNotes.md
# Export documentation to PDF/HTML (if export script exists)
cd Docs && ./export.sh
# Output: Docs/exports/ with timestamped HTML and PDF files| Component | Technology |
|---|---|
| Language | Swift 6 (strict concurrency) |
| UI Framework | SwiftUI |
| State Management | Observation framework (@Observable) |
| Persistence | SwiftData (iOS 17+) |
| Charts | Swift Charts |
| Testing | XCTest (unit + UI tests) |
| Architecture | Clean Architecture (3-layer) |
| Deployment | iOS 18.0+ |
| IDE | Xcode 16+ |
이 프로젝트는 Fastlane을 사용하여 빌드, 테스트, TestFlight 배포를 자동화합니다. PR마다 자동으로 테스트가 실행되며, v* 태그를 푸시하면 GitHub Actions가 TestFlight에 자동으로 배포합니다.
📘 자세한 가이드: Docs/Fastlane_Guide.md (환경 설정, 로컬 실행, CI 워크플로우, 트러블슈팅 포함)
로컬 실행 (3 commands):
bundle install # Ruby 의존성 설치
bundle exec fastlane tests # 테스트 실행
bundle exec fastlane beta # TestFlight 배포 (App Store Connect API 키 필요)- Fork & Clone: Fork this repo and clone locally
- Create Branch:
git checkout -b feat/your-feature-name - Follow Architecture: Respect Clean Architecture boundaries
- Add Tests: Write unit tests for new use cases
- Commit Convention: Use Conventional Commits
feat:New featurefix:Bug fixtest:Test updatesdocs:Documentation changesrefactor:Code refactoring
- Submit PR: Open pull request to
mainbranch
- Swift 6 strict concurrency mode
- No force unwraps (
!) in production code (useguard letorif let) - SwiftLint rules enforced (if configured)
- Domain layer purity: NO
import SwiftUIorimport SwiftDatain/Domain/
- Developer: Ben (오정석 / Oh Jungseok)
- Email: fiverights99@gmail.com
- GitHub Issues: https://github.com/jungseok-corine/WorkOut_Log/issues
- App Support: https://github.com/jungseok-corine/WorkOut_Log
- Privacy Policy: https://github.com/jungseok-corine/WorkOut_Log/blob/main/AppStore/PrivacyPolicy.md
Please open a GitHub issue with:
- iOS version
- Xcode version
- Steps to reproduce
- Expected vs actual behavior
- Screenshots (if applicable)
This project is licensed under the MIT License — see LICENSE file for details.
Copyright (c) 2025 Oh Jungseok (Ben)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files...
You are free to:
- ✅ Use commercially
- ✅ Modify
- ✅ Distribute
- ✅ Sublicense
Conditions:
- Include original license and copyright notice
- Provided "as is" without warranty
Developed by: Ben (오정석 / Oh Jungseok) Architecture Guidance: Claude (Anthropic AI Assistant) Design Philosophy: Privacy-first, offline-first, user-first Built with: ❤️ and Swift
Version: 1.0.0 Status: ✅ Ready for App Store submission Last Updated: 2025-10-19
- ✅ Implemented Sunday-Saturday weekly trend buckets
- ✅ Added two-line axis labels for Daily and Weekly trends
- ✅ Fixed AxisValueLabel overload ambiguity with explicit
content:parameter - ✅ Comprehensive unit tests for trend calculations
- ✅ Full VoiceOver accessibility support
⭐ Star this repo if you find it useful! 🐛 Found a bug? Open an issue 💬 Have questions? Start a discussion