How to Save a Uicolor with Userdefaults

How do I save a UIColor with UserDefaults?

Swift 5.2 or later

Note that this will save only the RGBA CGFloat values as Data inside the property list. This will use 32 bytes (raw data) instead of 424 bytes needed when using the standard approach with NSKeyedUnarchiver (NSCoding):

extension Numeric {
var data: Data {
var bytes = self
return Data(bytes: &bytes, count: MemoryLayout<Self>.size)
}
}


extension Data {
func object<T>() -> T { withUnsafeBytes{$0.load(as: T.self)} }
var color: UIColor { .init(data: self) }
}


extension UIColor {
convenience init(data: Data) {
let size = MemoryLayout<CGFloat>.size
self.init(red: data.subdata(in: size*0..<size*1).object(),
green: data.subdata(in: size*1..<size*2).object(),
blue: data.subdata(in: size*2..<size*3).object(),
alpha: data.subdata(in: size*3..<size*4).object())
}
var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)? {
var (red, green, blue, alpha): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
return getRed(&red, green: &green, blue: &blue, alpha: &alpha) ?
(red, green, blue, alpha) : nil
}
var data: Data? {
guard let rgba = rgba else { return nil }
return rgba.red.data + rgba.green.data + rgba.blue.data + rgba.alpha.data
}
}


extension UserDefaults {
func set(_ color: UIColor?, forKey defaultName: String) {
guard let data = color?.data else {
removeObject(forKey: defaultName)
return
}
set(data, forKey: defaultName)
}
func color(forKey defaultName: String) -> UIColor? {
data(forKey: defaultName)?.color
}
}


extension UserDefaults {
var backgroundColor: UIColor? {
get { color(forKey: "backgroundColor") }
set { set(newValue, forKey: "backgroundColor") }
}
}


UserDefaults.standard.backgroundColor = .red
UserDefaults.standard.backgroundColor // r 1.0 g 0.0 b 0.0 a 1.0

Save/Get UIColor from UserDefaults

In your code, simply replace 2 lines, i.e

Replace

color = NSKeyedUnarchiver.unarchiveObject(with: colorData) as? UIColor

with

color = try! NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: colorData)

//// Here you can use try? instead of try! and wrap it in if-let statement. Your choice.

and

Replace

colorData = NSKeyedArchiver.archivedData(withRootObject: color) as NSData?

with

colorData = try? NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: true)

Also, use Data instead of NSData in var colorData: NSData? ,i.e.

var colorData: Data?

Saving UIColor to and loading from NSUserDefaults

With the accepted answer, you'll quickly end up with a lot of NSKeyed archives & unarchives all over your code. A cleaner solution is to extend UserDefaults. This is exactly what extensions are for; UserDefaults probably doesn't know about UIColor as it is because UIKit and Foundation are different frameworks.

Swift

extension UserDefaults {

func color(forKey key: String) -> UIColor? {
var color: UIColor?
if let colorData = data(forKey: key) {
color = NSKeyedUnarchiver.unarchiveObject(with: colorData) as? UIColor
}
return color
}

func set(_ value: UIColor?, forKey key: String) {
var colorData: Data?
if let color = value {
colorData = NSKeyedArchiver.archivedData(withRootObject: color)
}
set(colorData, forKey: key)
}

}

Swift 4.2

extension UserDefaults {

func color(forKey key: String) -> UIColor? {

guard let colorData = data(forKey: key) else { return nil }

do {
return try NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: colorData)
} catch let error {
print("color error \(error.localizedDescription)")
return nil
}

}

func set(_ value: UIColor?, forKey key: String) {

guard let color = value else { return }
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false)
set(data, forKey: key)
} catch let error {
print("error color key data not saved \(error.localizedDescription)")
}

}

}

Usage

UserDefaults.standard.set(UIColor.white, forKey: "white")
let whiteColor = UserDefaults.standard.color(forKey: "white")

This can also be done in Objective-C with a category.

I've added the Swift file as a gist here.

Saving UIColor within Struct Array to UserDefaults

The errors are not related to my solution.

  • The first error tells you that the object is an array. Please read your code, tastings is clearly an array.

    So you have to decode an array

    let newTastings = try JSONDecoder().decode([Tasting].self, from: data)
  • The second error tells you that in your struct is a type which is not property list compliant. This type is UIColor. You cannot save Tasting instances to UserDefaults, but you can save JSON-/ or PropertyList-encoded Tasting instances.

     let data = try JSONEncoder().encode(tastings)
    UserDefaults.standard.set(data, forKey: "tastings")

Saving custom background color with userDefaults

I don't see the other buttons and where you change the background color but let's assume you are using an HeX represantation of a color ypu could save the background color in shared preferences and always use the shared state to set the background.

When you want to save the color to user prefs: UserDefaults.standard.set("#FF2200", forKey: "background_color_hex")

To retrieve the HEX represantation of background color from prefs:
let hexBackgroundColor = UserDefaults.standard.string(forKey: “background_color_hex”) ?? “#FFFFFF”.

Then to use it you can have an extension to get a UIColor based on its hex represantation like explained in many articles online, take for example this one https://www.hackingwithswift.com/example-code/uicolor/how-to-convert-a-hex-color-to-a-uicolor like this:
view.backgroundColor = UIColor(hex: hexBackgroundColor)

There can be a lot of other solutions I guess, this one is the quickest that came to mind.

How to save a color in NSUserDefaults in swift?

For Objective C

place like this.

NSData *colorData = [NSKeyedArchiver archivedDataWithRootObject:color];
[[NSUserDefaults standardUserDefaults] setObject:colorData forKey:@"myColor"];

To get back like this.

NSData *colorData = [[NSUserDefaults standardUserDefaults] objectForKey:@"myColor"];
UIColor *color = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];

For Swift

set like this

 var userSelectedColor : NSData? = (NSUserDefaults.standardUserDefaults().objectForKey("UserSelectedColor") as? NSData)

if (userSelectedColor != nil) {
var colorToSetAsDefault : UIColor = UIColor.redColor()
var data : NSData = NSKeyedArchiver.archivedDataWithRootObject(colorToSetAsDefault)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "UserSelectedColor")
NSUserDefaults.standardUserDefaults().synchronize()
println("SET DEFAULT USER COLOR TO RED")
}

get like this.

if let userSelectedColorData = NSUserDefaults.standardUserDefaults().objectForKey("UserSelectedColor") as? NSData {
if let userSelectedColor = NSKeyedUnarchiver.unarchiveObjectWithData(userSelectedColorData) as? UIColor {
println(userSelectedColor)
}
}

For Swift 4.2

place like this.

let colorToSetAsDefault : UIColor = UIColor.red
let data : Data = NSKeyedArchiver.archivedData(withRootObject: colorToSetAsDefault) as Data
UserDefaults.standard.set(data, forKey: "UserSelectedColor")
UserDefaults.standard.synchronize()
print("SET DEFAULT USER COLOR TO RED")

To get back like this.

if let userSelectedColorData = UserDefaults.standard.object(forKey: "UserSelectedColor") as? Data {
if let userSelectedColor = NSKeyedUnarchiver.unarchiveObject(with:userSelectedColorData as Data) as? UIColor {
print(userSelectedColor)
}
}


Related Topics



Leave a reply



Submit