Search code examples
swiftswiftuicolorsuserdefaults

Saving Color to UserDefaults and use it from @AppStorage


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 UserDefaults and read/write by UserDefaults.standard methods 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.standard and @AppStorage? I did try the extension methode from a posting around here, but I guess I did something very wrong, because it doesn't work with @AppStorage.

Using Xcode 13 and 14 for dev result for macOS 12 and iOS 15.


Solution

  • 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 are provided in this article.

    Create an extension:

    import SwiftUI
    import UIKit
        
        extension Color: RawRepresentable {
        public init?(rawValue: String) {
            guard let data = Data(base64Encoded: rawValue) else {
                self = .black
                return
            }
    
            do {
                if let color = try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) {
                    self = Color(color)
                } else {
                    self = .black
                }
            } 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)
            }
    }