Skip to content

jungseok-corine/WorkOut_Log

Repository files navigation

WorkOut Log

A privacy-first workout tracking app for iOS — No accounts, no tracking, completely offline

iOS Swift Xcode License


🏋️ Overview

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.

Core Philosophy

  • 🔒 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

✨ Features

Session & Set Tracking

  • 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

Exercise Library

  • 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

Trends & Analytics

  • 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

Personal Records

  • PR Tracking: Automatic personal record detection
  • Epley 1RM Formula: Estimated one-rep max calculation
  • Recent PR Highlights: See your latest achievements

Accessibility

  • 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

📸 Screenshots

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.


🏗️ Architecture

This app follows Clean Architecture principles with clear separation of concerns:

┌─────────────────┐    ┌──────────────┐    ┌─────────────────┐
│   Presentation  │───▶│    Domain    │◀───│      Data       │
│                 │    │              │    │                 │
│ • Views         │    │ • Entities   │    │ • Repositories  │
│ • ViewModels    │    │ • Use Cases  │    │ • SwiftData     │
│ • @Observable   │    │ • Protocols  │    │ • Mappers       │
└─────────────────┘    └──────────────┘    └─────────────────┘

Layer Responsibilities

Presentation Layer

  • SwiftUI views and navigation
  • @Observable ViewModels (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 @Model classes
  • Repository implementations
  • Domain ↔ Data mappers
  • Persistence layer (SwiftData ModelContext)

Key Design Decisions

📄 Architecture Decision Records (ADRs):


📦 Data Model

Core Entities

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
}

Relationships

Session (1) ─────────── (N) SetRecord
                           │
                           │ exerciseID
                           ▼
                        Exercise (1)

SwiftData Constraints

⚠️ Important Limitations:

  • No KeyPath chaining to value-type members (e.g., \.name.localizedLowercase)
  • FetchDescriptor.fetchLimit is a property, not a constructor parameter
  • #Predicate<Model> requires generic type annotation
  • Use SortDescriptor(\Model.property, order: .forward) for sorting

🚀 Development Setup

Prerequisites

  • 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 & Build

# 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 Tests

# 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 YES

Project Structure

WorkOut 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

🧪 Testing

Test Coverage

  • Domain Layer: 85%+ coverage (use cases, entities, business logic)
  • Data Layer: 75%+ coverage (repositories, SwiftData queries)
  • Presentation Layer: UI tests for critical workflows

Key Test Files

  • WeeklyBucketTests.swift: Verifies Sunday-Saturday week aggregation
  • DailyTrendAxisTests.swift: Validates ≤10 points cap and ordering
  • ExerciseRepositorySearchTests.swift: Tests client-side filtering
  • SessionListViewModelTests.swift: ViewModel state management

Running Specific Tests

# 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

♿ Accessibility

VoiceOver Support

  • 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.

Dynamic Type

  • 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)

High Contrast

  • Sufficient color contrast ratios (WCAG AA compliant)
  • Dark mode support with semantic colors
  • Focus indicators for keyboard navigation

Testing Accessibility

# 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

🔒 Privacy & Data

Zero Data Collection

  • 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

Data Storage

  • Local Only: All data stored on-device via SwiftData
  • iCloud Backup: Optional (user-controlled via iOS Settings)
  • No Cloud Sync: No custom cloud synchronization

Privacy Policy

Full privacy policy available at:

Summary: We collect zero data. Your workouts stay on your device.


📱 App Store Submission

Status

Ready for submission — All metadata, screenshots, and documentation prepared.

Submission Checklist

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.com

Metadata

All 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)

📚 Documentation

For Developers

  • Developer Guide: Comprehensive architecture guide
  • Tutorial (HTML): Printable beginner tutorial (A4-ready PDF export)
  • ADRs: Architecture decision records in /ADR/ directory

For Users

Generating Documentation

# Export documentation to PDF/HTML (if export script exists)
cd Docs && ./export.sh

# Output: Docs/exports/ with timestamped HTML and PDF files

🛠️ Tech Stack

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+

🚀 CI/CD & Fastlane

이 프로젝트는 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 키 필요)

🤝 Contributing

Development Workflow

  1. Fork & Clone: Fork this repo and clone locally
  2. Create Branch: git checkout -b feat/your-feature-name
  3. Follow Architecture: Respect Clean Architecture boundaries
  4. Add Tests: Write unit tests for new use cases
  5. Commit Convention: Use Conventional Commits
    • feat: New feature
    • fix: Bug fix
    • test: Test updates
    • docs: Documentation changes
    • refactor: Code refactoring
  6. Submit PR: Open pull request to main branch

Code Standards

  • Swift 6 strict concurrency mode
  • No force unwraps (!) in production code (use guard let or if let)
  • SwiftLint rules enforced (if configured)
  • Domain layer purity: NO import SwiftUI or import SwiftData in /Domain/

📧 Support

Contact

Support URLs

Reporting Bugs

Please open a GitHub issue with:

  • iOS version
  • Xcode version
  • Steps to reproduce
  • Expected vs actual behavior
  • Screenshots (if applicable)

📄 License

This project is licensed under the MIT License — see LICENSE file for details.

MIT License Summary

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

🙏 Credits

Developed by: Ben (오정석 / Oh Jungseok) Architecture Guidance: Claude (Anthropic AI Assistant) Design Philosophy: Privacy-first, offline-first, user-first Built with: ❤️ and Swift


📊 Project Status

Version: 1.0.0 Status: ✅ Ready for App Store submission Last Updated: 2025-10-19

Recent Updates

  • ✅ 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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages