6

I have an array of colors assigned that I want saved to UserDefaults. I have a default of gray, green, orange, and red, but I want to allow a color picker to change the green orange and red. The default value works and shows in my Simulator, but when I try to change a color in the array I get an error "Attempt to insert non-property list object (\n "50% gray",\n green,\n "kCGColorSpaceModelRGB 0.647194 0.881984 0.980039 1 ",\n red\n) for key SavedColors." I believe this is because the color picker is trying to insert a color of a different type? It looks like it is trying to insert a CGColor or CGColorSpace maybe?

Here is my code for the project:

import SwiftUI
import Foundation
import Combine

class UserSettings: ObservableObject {
    @Published var colors: [Color] {
        didSet {
            UserDefaults.standard.set(colors, forKey: "SavedColors")
        }
    }
    init() {
        self.colors = UserDefaults.standard.object(forKey: "SavedColors") as? [Color] ?? [Color.gray.opacity(0.5), Color.green, Color.orange, Color.red]
    }
}

struct CustomizeView: View {
    @ObservedObject var savedColors = UserSettings()
    var body: some View {
        NavigationView {
            Form {
                if #available(iOS 14.0, *) {
                    ColorPicker("Select low priority color", selection: $savedColors.colors[1])
                } else {
                    Text("Select low priority color")
                }
                if #available(iOS 14.0, *) {
                    ColorPicker("Select normal priority color", selection: $savedColors.colors[2])
                } else {
                    Text("Select normal priority color")
                }
                if #available(iOS 14.0, *) {
                    ColorPicker("Select high priority color", selection: $savedColors.colors[3])
                } else {
                    Text("Select high priority color")
                }
            }.navigationBarTitle("Customize", displayMode: .inline)
      }
}
Nate Rose
  • 61
  • 1
  • 3
  • No, it is because Color is not NSObject and cannot be placed into UserDefaults at all. You have to convert Color into UIColor, UIColor to Data and store last into UserDefaults. The restore is in reverse order. – Asperi Aug 07 '20 at 05:15
  • This might help you: [Saving UIColor to and loading from NSUserDefaults](https://stackoverflow.com/questions/1275662/saving-uicolor-to-and-loading-from-nsuserdefaults) and [How do I save a UIColor with UserDefaults?](https://stackoverflow.com/questions/34366171/how-do-i-save-a-uicolor-with-userdefaults) – pawello2222 Aug 07 '20 at 07:50

2 Answers2

5

Hi!

I could achieve saving SwiftUI Color with this conversion on MacOS:

extension Color {

    /// Explicitly extracted Core Graphics color
    /// for the purpose of reconstruction and persistance.
    var cgColor_: CGColor {
        NSColor(self).cgColor
    }
}

extension UserDefaults {
    func setColor(_ color: Color, forKey key: String) {
        let cgColor = color.cgColor_
        let array = cgColor.components ?? []
        set(array, forKey: key)
    }

    func color(forKey key: String) -> Color {
        guard let array = object(forKey: key) as? [CGFloat] else { return .accentColor }
        let color = CGColor(colorSpace: CGColorSpace(name: CGColorSpace.sRGB)!, components: array)!
        return Color(color)
    }
}
Ádám Nagy
  • 211
  • 4
  • 6
0

I'm doing this and it works:

@Published var usSearchTermColor: Color = Color(
    .sRGB,
    red: (UserDefaults.standard.object(forKey: "usSearchTermColor") as! [CGFloat])[0],
    green: (UserDefaults.standard.object(forKey: "usSearchTermColor") as! [CGFloat])[1],
    blue: (UserDefaults.standard.object(forKey: "usSearchTermColor") as! [CGFloat])[2],
    opacity: (UserDefaults.standard.object(forKey: "usSearchTermColor") as! [CGFloat])[3]
) {
    didSet {
        let components = self.usSearchTermColor.cgColor?.components ?? UIColor.yellow.cgColor.components

        UserDefaults.standard.set(components, forKey: "usSearchTermColor")
    }
}

Basically, storing the color information in UserDefaults as an array of CGFloat values, and assembling/disassembling whenever getting/setting.

I would be very happy to see a better way to do this!

Edit: credit to Indently @ this link for the concept.

protasm
  • 1,209
  • 12
  • 20