2

In my App for MacOS and iOS I use colors created from here: https://uiwjs.github.io/ui-color/ and then f.e. Works fine.

Color(red: 1.47, green: 1.9, blue: 2.3).opacity(1)

However for some colors I want them saved in the userDefaults and read/write by UserDefaults.standard methodes and read/write by @AppStorage.

I did try to use, but this gives me runtime errors.

static let infoListRowReadBGColor = Color(red: 2.55, green: 1.71, blue: 1.07).opacity(1)
static let infoListRowUnReadBGColor = Color(red: 2.55, green: 2.12, blue: 1.38).opacity(1)

var defaults = UserDefaults.standard

defaults.setValue(InAppDefaults.infoListRowReadBGColor, forKey: "infoListRowReadBGColor")
defaults.setValue(InAppDefaults.infoListRowUnReadBGColor, forKey: "infoListRowUnReadBGColor")
        

What do I need to change to get this working, read and write, using UserDefaults.default and @AppStore? I did try the extension methode from a posting around here, but I guess I do something very wrong, because it doesn't work with @AppStorage.

Using XCode 13 and 14 for dev result for MacOS 12 and iOS 15.

iPadawan
  • 898
  • 1
  • 12
  • 23
  • In general we cannot store Color directly into UserDefaults, so consider instead storing RGB and then create Color from restored RGB on the fly in view's body. – Asperi Jul 31 '22 at 15:12

3 Answers3

5

you can try converting color into data and store the data instead.

here's a uikit version extending UIColor you can use it for SwiftUI's Color too

import UIKit

extension UIColor {
    class func color(data: Data) -> UIColor {
        try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! UIColor
    }

    func encode() -> Data {
        try! NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
    }
}

you can persist the color using the encode function and once you retrieve the data, you can pass it on the class func to get the color

Blacksmith
  • 199
  • 7
2

You can't by default store Color() in UserDefaults, but you can use @AppStorage and NSKeyedArchiver to achieve this result. The full example and documentation is provided from this article.

Create an extension:

import Foundation
import SwiftUI
import UIKit

extension Color: RawRepresentable {

    public init?(rawValue: String) {
        
        guard let data = Data(base64Encoded: rawValue) else{
            self = .black
            return
        }
        
        do{
            let color = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor ?? .black
            self = Color(color)
        }catch{
            self = .black
        }
        
    }

    public var rawValue: String {
        
        do{
            let data = try NSKeyedArchiver.archivedData(withRootObject: UIColor(self), requiringSecureCoding: false) as Data
            return data.base64EncodedString()
            
        }catch{
            
            return ""
            
        }
        
    }

}

And use it as such:

@AppStorage("colorkey") var storedColor: Color = .black
    
    var body: some View {
        
        VStack{
            ColorPicker("Persisted Color Picker", selection: $storedColor, supportsOpacity: true)
            }
}
EJZ
  • 1,142
  • 1
  • 7
  • 26
  • 1
    Please do not post a link only answers. If the link is to expire it will render your answer incomplete. Please update your answer to include the relevant information – Andrew Jul 31 '22 at 16:43
  • @Andrew my answer has been edited as per your request. – EJZ Jul 31 '22 at 17:36
  • @EJZ, A question, why is the size of the saved rawdata so big? I did check it with the terminal defaults cmd. Should I save it as something else? – iPadawan Aug 01 '22 at 19:37
1

The answer that EJZ gives has put me on the right track. I've tried other methods as well, but the EJZ method I was able to use for both iOS and OSX with a little tweaking. Not wanting to edit his answer to keep that clear, I copied his part and my tweak into this answer.

I hope this helps others too. Thank you EJZ and others as well as the people for reading this too.

===

To split the os's I use an import distinction in the top/Import area of the file of the scene area part.

import Foundation
import SwiftUI
#if os(iOS)
    import UIKit
#elseif os(OSX)
    import AppKit
#endif

Here's the file I tweaked with the OS distinction

extension Color: RawRepresentable {

    public init?(rawValue: String) {
        guard let data = Data(base64Encoded: rawValue) else {
            self = .gray
            return
        }
        do{
#if os(iOS)
            let color = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor ?? .gray
#elseif os(OSX)
            let color = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? NSColor ?? .gray
#endif
            self = Color(color)
        }catch{
            self = .gray
        }
    }

    public var rawValue: String {
        do{
#if os(iOS)
            let data = try NSKeyedArchiver.archivedData(withRootObject: UIColor(self), requiringSecureCoding: false) as Data
#elseif os(OSX)
            let data = try NSKeyedArchiver.archivedData(withRootObject: NSColor(self), requiringSecureCoding: false) as Data
#endif

            return data.base64EncodedString()
        }catch{
            return ""
        }
    }
}

Both works well with the ( using the code of EJZ ) the @AppStorage SwiftUI views and both systems.

@AppStorage("key") var storedColor: Color = .gray

HOWEVER: why is the size of the saved rawdata so big?

iPadawan
  • 898
  • 1
  • 12
  • 23