- 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
120 lines
4.0 KiB
Swift
120 lines
4.0 KiB
Swift
import CoreData
|
|
import CloudKit
|
|
|
|
struct PersistenceController {
|
|
static let shared = PersistenceController()
|
|
|
|
let container: NSPersistentCloudKitContainer
|
|
|
|
// CloudKit container identifier
|
|
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)")
|
|
}
|
|
}
|
|
}
|
|
}
|