Обновить представление swiftUI при обновлении UserDefaults

Я полный новичок в Swift и SwiftUI, и я пытаюсь создать для себя проект, в котором я могу отслеживать свои тренировки и изучать Swift. Проблема, с которой я сталкиваюсь, заключается в том, что у меня есть представление, которое показывает все мои тренировки и форматируется с помощью параметра, который у меня есть в UserDefaults. Пользователь может изменить этот параметр на «метрическая» или «британская». При изменении этого параметра представление должно обновиться, чтобы отобразить эти изменения.

У меня есть следующие данные:

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 {

}

Еще я сделал себе помощника для 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
        }
    }


}

И это вид

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()
        }
    }
}

Проблема в том, что когда я меняю единицу измерения с британской на метрическую и возвращаюсь к просмотру сеанса, представление не обновляется, но когда я возвращаюсь к представлению тренировки и снова открываю представление сеанса, изменения уже есть.

Пожалуйста, дайте мне знать, если вам понадобится больше кода.


person andre de waard    schedule 08.03.2021    source источник
comment
Просто используйте AppStorage, как в stackoverflow.com/a/62716736/12299030   -  person Asperi    schedule 08.03.2021
comment
Спасибо за ответ, добавление AppStorage в мой помощник UserDefaults по-прежнему не обновляет мое представление. Возможно ли то, что я пытаюсь сделать? или я должен сделать formattedWeight функцию, которая принимает параметр massUnit и возвращает отформатированную строку?   -  person andre de waard    schedule 08.03.2021
comment
Почему не просто Text(session.formattedWeight)? Как вы меняете предпочтения по отношению к просмотру сеанса; вы скажете, когда я вернусь к просмотру сеанса, как вы вернетесь? Вы представляете новое представление или возвращаетесь к существующему виду?   -  person Paulw11    schedule 08.03.2021
comment
Да, я мог бы и это, но это ничего не меняет. Я возвращаюсь назад, я имею в виду, что у меня есть панель вкладок внутри ContentView, одна, на которой вы видите настройки, а другая - с тренировками, и с помощью этого обзора тренировок вы можете видеть сеансы этой тренировки. Поэтому, если я нахожусь на странице сведений о тренировке, чтобы увидеть сеансы, перейдите на страницу настроек, чтобы изменить единицы измерения, и вернусь к сеансам через панель вкладок, единицы не обновляются. Я исправил это сейчас, но включил @ObservedObject var userDefaultsWrapper = UserDefaultsWrapper() в это представление. который я не использую, но он обновляет представление.   -  person andre de waard    schedule 08.03.2021
comment
Да, текст был просто предложением упростить ваш код. Когда вы переключаетесь между вкладками, представления не обновляются, если SwiftUI не знает, что это необходимо. На вычисляемые свойства нельзя подписаться так, как можно подписать привязку, опубликованный или наблюдаемый объект.   -  person Paulw11    schedule 09.03.2021