Files
workouts/Workouts Watch App/Persistence/PersistenceController.swift
rzen 13313a32d3 Add CoreData-based workout tracking app with iOS and watchOS targets
- Migrate from SwiftData to CoreData with CloudKit sync
- Add core models: Split, Exercise, Workout, WorkoutLog
- Implement tab-based UI: Workout Logs, Splits, Settings
- Add SF Symbols picker for split icons
- Add exercise picker filtered by split with exclusion of added exercises
- Integrate IndieAbout for settings/about section
- Add Yams for YAML exercise definition parsing
- Include starter exercise libraries (bodyweight, Planet Fitness)
- Add Date extensions for formatting (formattedTime, isSameDay)
- Format workout date ranges to show time-only for same-day end dates
- Add build number update script
- Add app icons
2026-01-19 06:42:15 -05:00

120 lines
4.1 KiB
Swift

import CoreData
import CloudKit
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
// CloudKit container identifier - same as iOS app for sync
static let cloudKitContainerIdentifier = "iCloud.dev.rzen.indie.Workouts"
var viewContext: NSManagedObjectContext {
container.viewContext
}
// MARK: - Preview Support
static var preview: PersistenceController = {
let controller = PersistenceController(inMemory: true)
let viewContext = controller.container.viewContext
// Create sample data for previews
let split = Split(context: viewContext)
split.name = "Upper Body"
split.color = "blue"
split.systemImage = "dumbbell.fill"
split.order = 0
let exercise = Exercise(context: viewContext)
exercise.name = "Bench Press"
exercise.sets = 3
exercise.reps = 10
exercise.weight = 135
exercise.order = 0
exercise.loadType = Int32(LoadType.weight.rawValue)
exercise.split = split
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return controller
}()
// MARK: - Initialization
init(inMemory: Bool = false, cloudKitEnabled: Bool = true) {
container = NSPersistentCloudKitContainer(name: "Workouts")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("Failed to retrieve a persistent store description.")
}
if inMemory {
description.url = URL(fileURLWithPath: "/dev/null")
description.cloudKitContainerOptions = nil
} else if cloudKitEnabled {
// Check if CloudKit is available before enabling
let cloudKitAvailable = FileManager.default.ubiquityIdentityToken != nil
if cloudKitAvailable {
// Set CloudKit container options
let cloudKitOptions = NSPersistentCloudKitContainerOptions(
containerIdentifier: Self.cloudKitContainerIdentifier
)
description.cloudKitContainerOptions = cloudKitOptions
} else {
// CloudKit not available (not signed in, etc.)
description.cloudKitContainerOptions = nil
print("CloudKit not available - using local storage only")
}
// Enable persistent history tracking (useful even without CloudKit)
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
} else {
// CloudKit explicitly disabled
description.cloudKitContainerOptions = nil
}
container.loadPersistentStores { storeDescription, error in
if let error = error as NSError? {
// In production, handle this more gracefully
print("CoreData error: \(error), \(error.userInfo)")
#if DEBUG
fatalError("Unresolved error \(error), \(error.userInfo)")
#endif
}
}
// Configure view context
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// Pin the viewContext to the current generation token
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
print("Failed to pin viewContext to the current generation: \(error)")
}
}
// MARK: - Save Context
func save() {
let context = container.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nsError = error as NSError
print("Save error: \(nsError), \(nsError.userInfo)")
}
}
}
}