From ead9b2ebe0611d200b52b11af88e2ec00998c345 Mon Sep 17 00:00:00 2001 From: William Brawner Date: Thu, 5 Nov 2020 18:51:51 -0700 Subject: [PATCH] Split IntervalTimer model into view and coredata models Trying to use the same object for the view and Core Data was a bit clumsy and made working with SwiftUI previews difficult. Splitting them up fixed the issue at the expense of a little more maintenance work. --- IntervalTimer/ActiveTimerView.swift | 20 +++++++- IntervalTimer/IntervalTimer.swift | 43 ++++++++++++++++- .../IntervalTimer.xcdatamodel/contents | 2 +- IntervalTimer/TimerDataStore.swift | 47 +++++++++---------- IntervalTimer/TimerFormView.swift | 2 +- IntervalTimer/TimerListView.swift | 4 +- IntervalTimerTests/IntervalTimerTests.swift | 1 - 7 files changed, 88 insertions(+), 31 deletions(-) diff --git a/IntervalTimer/ActiveTimerView.swift b/IntervalTimer/ActiveTimerView.swift index f4164f4..4c6d84f 100644 --- a/IntervalTimer/ActiveTimerView.swift +++ b/IntervalTimer/ActiveTimerView.swift @@ -127,7 +127,25 @@ struct LabeledCounter: View { } struct ActiveTimerView_Previews: PreviewProvider { + static var dataStore: TimerDataStore { + get { + let store = TimerDataStore() {} + store.openTimer(IntervalTimer( + id: UUID(), + name: "Test", + description: nil, + warmUpDuration: 300, + lowIntensityDuration: 10, + highIntensityDuration: 20, + restDuration: 50, + cooldownDuration: 300, + sets: 4, + rounds: 2 + )) + return store + } + } static var previews: some View { - ActiveTimerView().environmentObject(TimerDataStore() {}) + ActiveTimerView().environmentObject(dataStore) } } diff --git a/IntervalTimer/IntervalTimer.swift b/IntervalTimer/IntervalTimer.swift index 648d8c7..5f5d5d9 100644 --- a/IntervalTimer/IntervalTimer.swift +++ b/IntervalTimer/IntervalTimer.swift @@ -11,7 +11,20 @@ import CoreData import Foundation import SwiftUI -class IntervalTimer: NSManagedObject, Identifiable { +struct IntervalTimer: Identifiable, Equatable { + let id: UUID? + let name: String + let description: String? + let warmUpDuration: Int64 + let lowIntensityDuration: Int64 + let highIntensityDuration: Int64 + let restDuration: Int64 + let cooldownDuration: Int64 + let sets: Int64 + let rounds: Int64 +} + +class IntervalTimerMO: NSManagedObject, Identifiable { @NSManaged var id: UUID? @NSManaged var name: String @NSManaged var userDescription: String? @@ -72,6 +85,34 @@ extension IntervalTimer { return warmUpDuration + ((((lowIntensityDuration + highIntensityDuration) * sets) + restDuration) * rounds) + cooldownDuration } } + + func copy(toMO: IntervalTimerMO) { + toMO.id = self.id + toMO.name = self.name + toMO.userDescription = self.description + toMO.warmUpDuration = self.warmUpDuration + toMO.lowIntensityDuration = self.lowIntensityDuration + toMO.highIntensityDuration = self.highIntensityDuration + toMO.restDuration = self.restDuration + toMO.cooldownDuration = self.cooldownDuration + toMO.sets = self.sets + toMO.rounds = self.rounds + } + + static func create(fromMO: IntervalTimerMO) -> IntervalTimer { + return IntervalTimer( + id: fromMO.id, + name: fromMO.name, + description: fromMO.userDescription, + warmUpDuration: fromMO.warmUpDuration, + lowIntensityDuration: fromMO.lowIntensityDuration, + highIntensityDuration: fromMO.highIntensityDuration, + restDuration: fromMO.restDuration, + cooldownDuration: fromMO.cooldownDuration, + sets: fromMO.sets, + rounds: fromMO.rounds + ) + } } struct ActiveTimerState { diff --git a/IntervalTimer/IntervalTimer.xcdatamodeld/IntervalTimer.xcdatamodel/contents b/IntervalTimer/IntervalTimer.xcdatamodeld/IntervalTimer.xcdatamodel/contents index ff327da..926c0c2 100644 --- a/IntervalTimer/IntervalTimer.xcdatamodeld/IntervalTimer.xcdatamodel/contents +++ b/IntervalTimer/IntervalTimer.xcdatamodeld/IntervalTimer.xcdatamodel/contents @@ -1,6 +1,6 @@ - + diff --git a/IntervalTimer/TimerDataStore.swift b/IntervalTimer/TimerDataStore.swift index 9b0250f..c8e30f7 100644 --- a/IntervalTimer/TimerDataStore.swift +++ b/IntervalTimer/TimerDataStore.swift @@ -183,18 +183,12 @@ class TimerDataStore: ObservableObject { } func loadTimers() { - DispatchQueue.global(qos: .background).async { - let fetchRequest = NSFetchRequest(entityName: "IntervalTimer") - do { - let fetchedTimers = try self.persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimer] - DispatchQueue.main.async { - self.timers = .success(fetchedTimers) - } - } catch { - DispatchQueue.main.async { - self.timers = .failure(.failed(error)) - } - } + let fetchRequest = NSFetchRequest(entityName: "IntervalTimer") + do { + let fetchedTimers = try self.persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimerMO] + self.timers = .success(fetchedTimers.map { IntervalTimer.create(fromMO: $0) }) + } catch { + self.timers = .failure(.failed(error)) } } @@ -210,18 +204,11 @@ class TimerDataStore: ObservableObject { sets: Int64, rounds: Int64 ) { - var timer: IntervalTimer + var timer: IntervalTimerMO if let uuid = id { - let fetchRequest = NSFetchRequest(entityName: "IntervalTimer") - fetchRequest.predicate = NSPredicate(format: "id = %@", uuid.uuidString) - let result = try! persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimer] - if result.count == 1 { - timer = result[0] - } else { - timer = IntervalTimer.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimer - } + timer = loadTimer(byId: uuid)! } else { - timer = IntervalTimer.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimer + timer = IntervalTimerMO.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimerMO } timer.id = id ?? UUID() @@ -242,19 +229,31 @@ class TimerDataStore: ObservableObject { loadTimers() if let currentState = activeTimer { if currentState.timer.id == timer.id { - activeTimer = currentState.copy(timer: timer) + activeTimer = currentState.copy(timer: IntervalTimer.create(fromMO: timer)) } } } func deleteTimer(at: IndexSet) { - let timer = try! self.timers.get()[at.first!] + guard let timer = loadTimer(byId: try! self.timers.get()[at.first!].id!) else { + return + } let viewContext = persistentContainer.viewContext viewContext.delete(timer) try! viewContext.save() loadTimers() } + private func loadTimer(byId: UUID) -> IntervalTimerMO? { + let fetchRequest = NSFetchRequest(entityName: "IntervalTimer") + fetchRequest.predicate = NSPredicate(format: "id = %@", byId.uuidString) + let result = try! persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimerMO] + if result.count == 1 { + return result[0] + } + return nil + } + private func loadSound(_ phase: Phase) { let filePath = Bundle.main.path(forResource: phase.rawValue, ofType: "mp3") let url = NSURL(fileURLWithPath: filePath!) diff --git a/IntervalTimer/TimerFormView.swift b/IntervalTimer/TimerFormView.swift index 6458189..0414f14 100644 --- a/IntervalTimer/TimerFormView.swift +++ b/IntervalTimer/TimerFormView.swift @@ -162,7 +162,7 @@ struct TimerFormView: View { self.title = "New Timer" } self._name = State(initialValue: timer?.name ?? "") - self._description = State(initialValue: timer?.userDescription ?? "") + self._description = State(initialValue: timer?.description ?? "") self._warmDuration = State(initialValue: timer?.warmUpDuration ?? 300) self._lowDuration = State(initialValue: timer?.lowIntensityDuration ?? 30) self._highDuration = State(initialValue: timer?.highIntensityDuration ?? 60) diff --git a/IntervalTimer/TimerListView.swift b/IntervalTimer/TimerListView.swift index e9347c4..2d5eccf 100644 --- a/IntervalTimer/TimerListView.swift +++ b/IntervalTimer/TimerListView.swift @@ -29,8 +29,8 @@ struct TimerListView: View { VStack(alignment: .leading) { Text(timer.name) .lineLimit(1) - if timer.userDescription?.count ?? 0 > 0 { - Text(timer.userDescription!) + if timer.description?.count ?? 0 > 0 { + Text(timer.description!) .font(.subheadline) .foregroundColor(.secondary) .lineLimit(1) diff --git a/IntervalTimerTests/IntervalTimerTests.swift b/IntervalTimerTests/IntervalTimerTests.swift index 5f544cc..217ed39 100644 --- a/IntervalTimerTests/IntervalTimerTests.swift +++ b/IntervalTimerTests/IntervalTimerTests.swift @@ -30,5 +30,4 @@ class IntervalTimerTests: XCTestCase { // Put the code you want to measure the time of here. } } - }