Storing Uicolor Object in Core Data

Storing UIColor object in Core Data

Your attribute should be of the type Transformable. The default value transformer (NSKeyedUnarchiveFromDataTransformerName) can transform any object that conforms to NSCoding.

  1. Mark the attribute as type "Tranformable".
  2. Optional: Set the Value Transformer Name to "NSKeyedUnarchiveFromDataTransformerName". If you do not, it will default to this anyway.

Like so

You do not have to do anything else. Keep in mind you will not be able to match transformable attribute with a predicate or sort by them. They are pretty much just storage - the value transformer transforms the object value into NSData, which is what gets persisted in the store. When the attribute fault fires Core Data uses the transformer in the other direction to go from NSData to your object type.

Store and retrieve UIColor from Core Data using swift 5

Try the following for Swift 5

extension UIColor {

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

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

Best way to save and retrieve UIColors to Core Data

You can convert your UIColor to NSData and then store it:

NSData *theData = [NSKeyedArchiver archivedDataWithRootObject:[UIColor blackColor]];

then you can convert it back to your UIColor object:

UIColor *theColor = (UIColor *)[NSKeyedUnarchiver unarchiveObjectWithData:theData];

UPD.:

Answering your question in comments about storing the data, the most simple way is to store it in NSUserDefaults:

NSData *theData = [NSKeyedArchiver archivedDataWithRootObject:[UIColor blackColor]];
[[NSUserDefaults standardUserDefaults] setObject:theData forKey:@"myColor"];

and then retrive it:

NSData *theData = [[NSUserDefaults standardUserDefaults] objectForKey:@"myColor"];
UIColor *theColor = (UIColor *)[NSKeyedUnarchiver unarchiveObjectWithData:theData];

How to store type Color from SwiftUI into CoreData?

You can use Transformable, although it has to be stored as UIColor and not Color:

The withRootObject is the colour you want to save.

Also keep in mind that myColour should be of type Data:

@NSManaged public var myColour: Data?

Then you can archive the data like the following:

do {
try obj.myColour = NSKeyedArchiver.archivedData(withRootObject: UIColor.blue, requiringSecureCoding: false)
} catch {
print(error)
}

Retrieve it using:

func getColour(data: Data) -> Color {
do {
return try Color(NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data)!)
} catch {
print(error)
}

return Color.clear
}

Usage:

Text("This is some blue text.") // we saved it as UIColor.blue earlier.
.foregroundColor(self.getColour(data: self.data.myColour!))

iOS 11 - Core Data - UIColor no longers works as transformable attribute

Well Apple got back to me, there are new persistentStore options!

The text I got from apple:

/* Allows developers to provide an additional set of classes (which
must implement NSSecureCoding) that should be used while decoding a
binary store. Using this option is preferable to using
NSBinaryStoreInsecureDecodingCompatibilityOption.
*/ COREDATA_EXTERN NSString * const NSBinaryStoreSecureDecodingClasses
API_AVAILABLE(macosx(10.13),ios(11.0),tvos(11.0),watchos(4.0));

/* Indicate that the binary store should be decoded insecurely. This
may be necessary if a store has metadata or transformable properties
containing non-standard classes. If possible, developers should use
the NSBinaryStoreSecureDecodingClasses option to specify the contained
classes, allowing the binary store to to be securely decoded.
Applications linked before the availability date will default to using
this option.
*/ COREDATA_EXTERN NSString * const NSBinaryStoreInsecureDecodingCompatibilityOption
API_AVAILABLE(macosx(10.13),ios(11.0),tvos(11.0),watchos(4.0));

It's not immediately clear, but basically you have to supply an NSSet of classes you use as transformable attributes that comply with NSSecureCoding as an option when opening your persistent store.

An example for mine using the UIColor :

NSError *localError;
NSDictionary *options;
if (@available(iOS 11.0, *)) {
options = @{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
NSBinaryStoreSecureDecodingClasses : [NSSet setWithObjects:[UIColor class], nil]
};

} else {
// Fallback on earlier versions
options = @{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
};
}
NSPersistentStore *newStore = [self.psc addPersistentStoreWithType:NSBinaryStoreType configuration:@"iOS" URL:psURL options:options error:&localError];

EDIT: Adding a solution for the newer way to open core data persistent stores using NSPersistentStoreDescription. This code is based on the current core data template.

- (NSPersistentContainer *)persistentContainer {
// The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
@synchronized (self) {
if (_persistentContainer == nil) {
NSURL *defaultURL = [NSPersistentContainer defaultDirectoryURL];
defaultURL = [defaultURL URLByAppendingPathComponent:@"CoreDataTransformableAttribBug.binary"];
_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"CoreDataTransformableAttribBug"];
NSPersistentStoreDescription *desc = [NSPersistentStoreDescription persistentStoreDescriptionWithURL:defaultURL];

desc.type = NSBinaryStoreType;
if (@available(iOS 11.0, *)) {
[desc setOption:[NSSet setWithObjects:[UIColor class], nil] forKey:NSBinaryStoreSecureDecodingClasses];
}
_persistentContainer.persistentStoreDescriptions = @[desc];
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
NSLog(@"Unresolved error %@, %@", error, error.userInfo);
abort();
} else {
NSLog(@"Description = %@", storeDescription);
}
}];
}
}

return _persistentContainer;
}

I also updated my gitHub project with the fix in a branch

How can I correctly save transformable objects in core data?

The linked article covers the topic very well. The log in the console you are getting is not an error, but a warning. We have encountered the same issue and every solution points to using NSSecureUnarchiveFromData which in fact seems to be a correct one for the problem. We have been using it in production for quite a while now and everything works as expected and the warning is gone. And you should be treating it for what it is, a warning. It it does resurface at any time for some specific type that you are trying to store in your CoreData, you could try implementing a custom transformer just like its shown in the article. Good luck!

Saving [UIColor colorWithPatternImage:image] UIColor to Core Data using NSKeyedArchiver

Oh Yes! I got it. With a lot of help from the following people/posts...

Gave me the idea to use associatedObjects

Explanation of associatedObjects

and method swizzling

Create a category on UIColor. Use an Associated Object to set a reference to the pattern image in the UIColor instance (kind of like a dynamic property), don't forget to import <objc/runtime.h>. When you create your UIColor color = [UIColor colorWithPatternImage:selectedImage], also set the associated object on the color [color setAssociatedObject:selectedImage].

Then implement custom encodeWithCoder and initWithCoder methods in the category to serialize the UIImage.

And finally do some method swizzling in the main.m file so you can invoke the original UIColor encodeWithCoder and initWithCoder methods from within your UIColor Category. Then you don't even need to write your own Value Transformer for Core Data because UIColor implements the NSCoding protocol. Code below...

UIColor+patternArchive

#import "UIColor+patternArchive.h"
#import <objc/runtime.h>

@implementation UIColor (UIColor_patternArchive)

static char STRING_KEY; // global 0 initialization is fine here, no
// need to change it since the value of the
// variable is not used, just the address

- (UIImage*)associatedObject
{
return objc_getAssociatedObject(self,&STRING_KEY);
}

- (void)setAssociatedObject:(UIImage*)newObject
{
objc_setAssociatedObject(self,&STRING_KEY,newObject,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)encodeWithCoderAssociatedObject:(NSCoder *)aCoder
{
if (CGColorSpaceGetModel(CGColorGetColorSpace(self.CGColor))==kCGColorSpaceModelPattern)
{
UIImage *i = [self associatedObject];
NSData *imageData = UIImagePNGRepresentation(i);
[aCoder encodeObject:imageData forKey:@"associatedObjectKey"];
self = [UIColor clearColor];
} else {

// Call default implementation, Swizzled
[self encodeWithCoderAssociatedObject:aCoder];
}
}

- (id)initWithCoderAssociatedObject:(NSCoder *)aDecoder
{
if([aDecoder containsValueForKey:@"associatedObjectKey"])
{
NSData *imageData = [aDecoder decodeObjectForKey:@"associatedObjectKey"];
UIImage *i = [UIImage imageWithData:imageData];
self = [[UIColor colorWithPatternImage:i] retain];
[self setAssociatedObject:i];
return self;
}
else
{
// Call default implementation, Swizzled
return [self initWithCoderAssociatedObject:aDecoder];
}
}

main.m

#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import "UIColor+patternArchive.h"

int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

// Swizzle UIColor encodeWithCoder:
Method encodeWithCoderAssociatedObject = class_getInstanceMethod([UIColor class], @selector(encodeWithCoderAssociatedObject:));
Method encodeWithCoder = class_getInstanceMethod([UIColor class], @selector(encodeWithCoder:));
method_exchangeImplementations(encodeWithCoder, encodeWithCoderAssociatedObject);

// Swizzle UIColor initWithCoder:
Method initWithCoderAssociatedObject = class_getInstanceMethod([UIColor class], @selector(initWithCoderAssociatedObject:));
Method initWithCoder = class_getInstanceMethod([UIColor class], @selector(initWithCoder:));
method_exchangeImplementations(initWithCoder, initWithCoderAssociatedObject);

int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}


Related Topics



Leave a reply



Submit