I want to store an array of non-Codable
structures (= HabitConfiguration()
) in UserDefaults
.
Here is my attempt which consists in placing in a "dictionary" the variables of this structure before storing it in UserDefaults
. There is no explicit error in my code but executing it crashes my application.
The model of structures to save in an array (not compliant to Codable
):
struct HabitConfiguration {
var name: String = ""
var description: String = ""
var appearance: String = "planet"
var planetColor: Color = Color("PlanetColor1")
var planetHasRing: Bool = false
var planetRingStyle = PlanetRingStyle()
var planetRotation: Double = 0
var emoji: String = "🚦"
var repetitionType: RepetitionType = .unique(date: [UniqueDate(day: 1, month: 1, time: Time(hours: 12, minutes: 0))])
}
Additional code needed for the previous structure:
struct PlanetRingStyle {
var lineWidth: Double = 5
var lineCap: CGLineCap = .round
var dash: [CGFloat] = [1]
}
enum RepetitionType {
case daily(time: Time)
case weekly(days: Days, time: Time)
case monthly(dates: DaysNumbers, time: Time)
case unique(date: [UniqueDate])
}
extension RepetitionType {
var isDaily: Bool {
if case .daily = self { return true }
return false
}
var isWeekly: Bool {
if case .weekly = self { return true }
return false
}
var isMonthly: Bool {
if case .monthly = self { return true }
return false
}
var isUnique: Bool {
if case .unique = self { return true }
return false
}
}
"Dictionary" of structure values:
extension HabitConfiguration {
func toDictionary() -> [String: Any] {
return [
"name": name,
"description": description,
"appearance": appearance,
"planetColor": planetColor.description,
"planetHasRing": planetHasRing,
"planetRingStyle": planetRingStyle,
"planetRotation": planetRotation,
"emoji": emoji,
"repetitionType": repetitionType
]
}
init?(from dictionary: [String: Any]) {
guard
let name = dictionary["name"] as? String,
let description = dictionary["description"] as? String,
let appearance = dictionary["appearance"] as? String,
let planetColorDescription = dictionary["planetColor"] as? String,
let planetHasRing = dictionary["planetHasRing"] as? Bool,
let planetRingStyle = dictionary["planetRingStyle"] as? Int,
let planetRotation = dictionary["planetRotation"] as? Double,
let emoji = dictionary["emoji"] as? String,
let repetitionType = dictionary["repetitionType"] as? RepetitionType
else {
return nil
}
self.name = name
self.description = description
self.appearance = appearance
self.planetColor = Color(planetColorDescription)
self.planetHasRing = planetHasRing
self.planetRingStyle = PlanetRingStyle(lineWidth: 0, lineCap: .round, dash: [0])
self.planetRotation = planetRotation
self.emoji = emoji
self.repetitionType = repetitionType
}
}
Read and write the array stored in UserDefaults:
struct HabitsList {
private let habitsKey = "habitsList"
func loadHabits() -> [HabitConfiguration] {
guard let savedHabits = UserDefaults.standard.array(forKey: habitsKey) as? [[String: Any]] else {
return []
}
return savedHabits.compactMap { HabitConfiguration(from: $0) }
}
func saveHabits(_ habits: [HabitConfiguration]) {
let habitDictionaries = habits.map { $0.toDictionary() }
UserDefaults.standard.set(habitDictionaries, forKey: habitsKey)
}
func habitsList() -> Binding<[HabitConfiguration]> {
Binding(
get: { self.loadHabits() },
set: { self.saveHabits($0) }
)
}
}
UserDefaults
is not the thing to use here. The correct thing to use is either a local CoreData Store or using a custom JSON File (requires Codable compliance).
Codable compliance is also really easy to add to an existing struct.
The hurdle you are probably facing there is adding support for Color, other than that you can just mark the struct PlanetRingStyle
and enum RepititionType
as well as the other structs/enums used as Codable as well and it should be smooth sailing from there.
If you are already implementing Codable compliance you could also use @AppStorage