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.
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)
}
}
Swift Swiftui - Saving Color to UserDefaults and use it from @AppStorage
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)
}
}
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.
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 arraylet 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 saveTasting
instances toUserDefaults
, but you can save JSON-/ or PropertyList-encodedTasting
instances.let data = try JSONEncoder().encode(tastings)
UserDefaults.standard.set(data, forKey: "tastings")
Related Topics
Custom Radix Columns (+Special Characters)
How to Override Setter in Swift
Nsdatepicker in Nsstatusbar Nssmenuitem Not Receiving Input
"'Init' Is Deprecated" Warning After Swift4 Convert
Error "[Sharesheet] Connection Invalidated" Error iOS13+ But Not on iOS 11.4
Wkwebview Won't Load (Nsviewcontroller, Os X)
Get Png Representation of Nsimage in Swift
Can't Get Throws to Work with Function with Completion Handler
How to Update UIviewrepresentable with Observableobject
What Was The Reason for Swift Assignment Evaluation to Void
When How to Start Submitting Apps to The iOS App Store Written Using The Swift Programming Language
Check Os Version Using Swift on MAC Os X
Sknode Subclass Generates Error: Cannot Invoke Initializer for Type "X" with No Arguments
How to Get The Count of a Type Conforming to 'sequence'
Move Button When Keyboard Appears Swift
How to Create an iOS Liveview in Xcode 8/Swift 3
Convert Time String into Date Swift
Filter on Calayer Except for a Shape Which Is an Union of (Non Necessarily Distinct) Rectangles