I\'m just starting out with Core Data and right now I\'m building my data model. I need a UIColor attribute for my entity, but the type dropdown for the attribute doesn\'t h
What you probably want is a transformable attribute. Give the section on "Non-standard Persistent Attributes" in the Core Data Programming Guide an other read. A transformable attribute is, underneath the covers, a binary data attribute, but Core Data will automatically use the NSValueTransformer
of your specification to serialize and unserialize the logical attribute value for you. For values that are NSCoding
compliant, the NSKeyedUnarchiveFromDataTransformerName
(which is the default transformer) will do the trick.
Of course, Core Data cannot index or, for an SQLite backend, query against this transformable value.
After specify color attribute as transformable with name ColorToDataTransformer. We can simply generate MangatedObjectSubclass and write transformer code inside it
//SampleEntity.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface SampleEntity : NSManagedObject
@property (nonatomic, retain) UIColor *color;
@end
@interface ColorToDataTransformer : NSValueTransformer
@end
//SampleEntity.m
#import "SampleEntity.h"
@implementation SampleEntity
@dynamic color;
@end
@implementation ColorToDataTransformer
+ (BOOL)allowsReverseTransformation {
return YES;
}
+ (Class)transformedValueClass {
return [NSData class];
}
- (id)transformedValue:(id)value {
UIColor *color = (UIColor *)value;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:color];
return data;
}
- (id)reverseTransformedValue:(id)value {
NSData *data = (NSData *)value;
UIColor *color = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return color;
}
@end
I'll paraphrase the definitve answer I found in More iPhone 3 Development by Dave Mark and Jeff LeMarche:
Usually we'd be able to leave the transformable attribute's transformer class as the default, NSKeyedUnarchiveFromData, and be done, but in this case we can't because UIColor
doesn't conform to NSCoding
and can't be archived using an NSKeyedArchiver
. We have to manually write a value transformer to handle the transformation.
Add an attribute to your entity and call the attribute "color", or whatever name you wish. Set its type to Transformable. Set its "Value Transformer Name" to UIColorRGBValueTransformer. Note that the data model editor doesn't validate the Value Transformer Name: to make sure it's a valid class, so type carefully.
Create a new file, a subclass of NSObject
, and name it UIColorRGBValueTransformer.m.
Click UIColorRGBValueTransformer.h and change the superclass from NSObject to NSValueTransformer. Also, change #import <Foundation/Foundation.h>
to #import <UIKit/UIKit.h>
, since UIColor
is part of UIKit
, not Foundation
.
Now in UIColorRGBValueTransformer.m, we need to implement four methods that allow our value transformer class to convert instances of UIColor
to NSData
and vice versa. Include the following code in UIColorRGBValueTransformer.m:
#import "UIColorRGBValueTransformer.h"
@implementation UIColorRGBValueTransformer
// Here we override the method that returns the class of objects that this transformer can convert.
+ (Class)transformedValueClass {
return [NSData class];
}
// Here we indicate that our converter supports two-way conversions.
// That is, we need to convert UICOLOR to an instance of NSData and back from an instance of NSData to an instance of UIColor.
// Otherwise, we wouldn't be able to beth save and retrieve values from the persistent store.
+ (BOOL)allowsReversTransformation {
return YES;
}
// Takes a UIColor, returns an NSData
- (id)transfomedValue:(id)value {
UIColor *color = value;
const CGFloat *components = CGColorGetComponents(color.CGColor);
NSString *colorAsString = [NSString stringWithFormat:@"%f,%f,%f,%f", components[0], components[1], components[2], components[3]];
return [colorAsString dataUsingEncoding:NSUTF8StringEncoding];
}
// Takes an NSData, returns a UIColor
- (id)reverseTransformedValue:(id)value {
NSString *colorAsString = [[[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding] autorelease];
NSArray *components = [colorAsString componentsSeparatedByString:@","];
CGFloat r = [[components objectAtIndex:0] floatValue];
CGFloat g = [[components objectAtIndex:1] floatValue];
CGFloat b = [[components objectAtIndex:2] floatValue];
CGFloat a = [[components objectAtIndex:3] floatValue];
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
@end
Now in another file, you can include a line of code like:
[self.managedObject setValue:color forKey:self.keyPath];
without needing to import UIColorRGBValueTransformer.h in the file.
Here it is a Swift's Version of @RosePerrone answers. I Hope it helps.
class ColorToDataTransformer: ValueTransformer {
// Here we indicate that our converter supports two-way conversions.
// That is, we need to convert UICOLOR to an instance of NSData and back from an instance of NSData to an instance of UIColor.
// Otherwise, we wouldn't be able to beth save and retrieve values from the persistent store.
override class func allowsReverseTransformation() -> Bool {
return true
}
override class func transformedValueClass() -> AnyClass {
return NSData.self
}
// Takes a UIColor, returns an NSData
override func transformedValue(_ value: Any?) -> Any? {
guard let color = value as? UIColor else { return nil }
guard let components: [CGFloat] = color.cgColor.components else { return nil }
let colorAsString: String = String(format: "%f,%f,%f,%f", components[0], components[1], components[2], components[3])
return colorAsString.data(using: .utf8)
}
// Takes an NSData, returns a UIColor
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? Data else { return nil }
guard let colorAsString = String(data: data, encoding: .utf8) else { return nil }
let componets: [String] = colorAsString.components(separatedBy: ",")
var values: [Float] = []
for component in componets {
guard let value = Float(component) else { return nil }
values.append(value)
}
let red: CGFloat = CGFloat(values[0])
let green: CGFloat = CGFloat(values[1])
let blue: CGFloat = CGFloat(values[2])
let alpha: CGFloat = CGFloat(values[3])
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}
Is it possible to store the UIColor as a String as a HEX (FFFFFF, or other web-safe colors) and then when you come to read the color, you convert the string into a format that UIColor can understand?