Im a total noob to Swift and SwiftUI and im trying to build a project for myself where i can track my workouts and learn myself some Swift. The problem im hitting is that i have a view which shows all my workout sessions and are formatted by a setting i have in UserDefaults. The user can change this setting to 'metric' or 'imperial' When changing this the view should update to represent those changes.
The data i have is:
extension WorkoutSession {
@nonobjc public class func fetchRequest() -> NSFetchRequest<WorkoutSession> {
return NSFetchRequest<WorkoutSession>(entityName: "WorkoutSession")
}
@NSManaged public var created_at: Date?
@NSManaged public var id: UUID?
@NSManaged public var reps: Int16
@NSManaged public var sets: Int16
@NSManaged public var updated_at: Date?
@NSManaged public var weight: Double
@NSManaged public var height: Double
@NSManaged public var notes: String?
@NSManaged public var type: Workout?
override public func awakeFromInsert() {
super.awakeFromInsert()
setPrimitiveValue(UUID(), forKey: "id")
setPrimitiveValue(Date(), forKey: "updated_at")
}
override public func willSave() {
super.willSave()
if let updated_at = updated_at {
if updated_at.timeIntervalSince(Date()) > 10.0 {
self.updated_at = Date()
}
} else {
self.updated_at = Date()
}
}
var formattedWeight: String {
var measurement = Measurement(value: self.weight, unit: UserDefaultsWrapper().savedUnitWeight)
let numberFormatter = NumberFormatter()
let measurementFormatter = MeasurementFormatter()
measurement.convert(to:UserDefaultsWrapper().getWeightUnit)
numberFormatter.maximumFractionDigits = 2
measurementFormatter.unitOptions = .providedUnit
measurementFormatter.numberFormatter = numberFormatter
return measurementFormatter.string(from: measurement)
}
// No matter the user defined weight we always save the values in Kilogram so we can convert to anything afterwards
func convertWeightFromUserDefinedUnitToKilogram(weight: Double) -> Double {
let measurement = Measurement(value: weight, unit: UserDefaultsWrapper().getWeightUnit)
return measurement.converted(to: UserDefaultsWrapper().savedUnitWeight).value
}
}
extension WorkoutSession : Identifiable {
}
I also made myself a helper for userDefaults
import SwiftUI
import Foundation
enum UserDefaultsKeys: String {
case measurementUnit = "measurementUnit"
}
enum measurementUnit: String, CaseIterable {
case metric = "metric"
case imperial = "Imperial"
}
struct UserDefaultsWrapper {
let defaults = UserDefaults.standard
var savedUnitWeight = UnitMass.kilograms
var savedUnitHeight = UnitLength.meters
@AppStorage(UserDefaultsKeys.measurementUnit.rawValue) var selectedUnit: String = measurementUnit.metric.rawValue
var getMeasurementUnit: measurementUnit {
get {
let locale = Locale.current
let systemMeasurementUnit = locale.usesMetricSystem ? measurementUnit.metric : measurementUnit.imperial
return measurementUnit(rawValue: selectedUnit) ?? systemMeasurementUnit
}
set(unit) {
defaults.set(unit.rawValue, forKey: UserDefaultsKeys.measurementUnit.rawValue)
}
}
var getWeightUnit: UnitMass {
switch getMeasurementUnit {
case measurementUnit.imperial:
return UnitMass.pounds
case measurementUnit.metric:
fallthrough
default:
return UnitMass.kilograms
}
}
var getHeightUnit: UnitLength {
switch getMeasurementUnit {
case measurementUnit.imperial:
return UnitLength.feet
case measurementUnit.metric:
fallthrough
default:
return UnitLength.meters
}
}
}
And this is the view
import SwiftUI
struct WorkoutDetailView: View {
@Environment(\.managedObjectContext) var moc
@State private var showingAddWorkoutView = false
@ObservedObject var workout: Workout
var body: some View {
List {
ForEach(workout.sessionsArray, id: \.id) { session in
Text("\(session.formattedWeight)")
}
.onDelete(
perform: { offsets in
self.removeItems(at: offsets, from: workout)
}
)
}
.listStyle(InsetGroupedListStyle())
.navigationTitle(workout.name ?? "Unknown Workout")
.navigationBarItems(
leading: EditButton(),
trailing:
Button(action: {
self.showingAddWorkoutView = true
}) {
Image(systemName: "plus")
}
)
.sheet(isPresented: $showingAddWorkoutView) {
AddWorkoutSession(workout: workout)
.environment(\.managedObjectContext, moc)
}
}
func removeItems(at offsets: IndexSet, from workout: Workout) {
for offset in offsets {
let sessionToDelete = workout.sessionsArray[offset]
workout.removeFromSessions(sessionToDelete)
moc.delete(sessionToDelete)
}
if moc.hasChanges{
try? moc.save()
}
}
}
The problem is that when i change the unit from imperial to metric and i go back to the session view the view is not updated, but when i go back to the workout view and open the session view again the changes are there.
Please let me know if you need more code.