Why is my transformable Core Data attribute not using my custom NSValueTransformer?

26,061

Solution 1

It turns out that this is actually a bug in the frameworks. See this post by an Apple employee on the Cocoa-Dev mailing list:

http://lists.apple.com/archives/Cocoa-dev/2009/Dec/msg00979.html

Solution 2

If I'm not mistaken, your value transformer has a reversed direction. I do the same thing in my application, using code like the following:

+ (Class)transformedValueClass 
{
 return [NSData class]; 
}

+ (BOOL)allowsReverseTransformation 
{
 return YES; 
}

- (id)transformedValue:(id)value 
{
 if (value == nil)
  return nil;

 // I pass in raw data when generating the image, save that directly to the database
 if ([value isKindOfClass:[NSData class]])
  return value;

 return UIImagePNGRepresentation((UIImage *)value);
}

- (id)reverseTransformedValue:(id)value
{
 return [UIImage imageWithData:(NSData *)value];
}

While this is for the iPhone, you should be able to swap in your NSImage code at the appropriate locations. I simply haven't tested my Mac implementation yet.

All I did to enable this was to set my image property to be transformable within the data model, and specify the name of the transformer. I didn't need to manually register the value transformer, as you do in your +initialize method.

Solution 3

It seems registering the transformer has no effect on wether Core Data will use it or not. I've been playing with the PhotoLocations sample code and removing the transformer registration has no effect. As long as your ValueTransformerName is the name of your class, and that your transformer's implementation is included with the target, it should work.

If there is no code execution in the transformer, then the code in the transformer is irrelevant. The problem must be somewhere else in the core data stack, or in the declaration of the transformer.

Solution 4

You need to explicitly register your transformer during runtime.

Good place to do this is to override Class initialize method in your NSManagedObject entity subclass. As mentioned before it is known Core Data bug. Following is the crucial code from Apple's location code sample, it is tested and works: http://developer.apple.com/library/ios/#samplecode/Locations/Introduction/Intro.html

+ (void)initialize {
    if (self == [Event class]) {
        UIImageToDataTransformer *transformer = [[UIImageToDataTransformer alloc] init];
        [NSValueTransformer setValueTransformer:transformer forName:@"UIImageToDataTransformer"];
    }
}
Share:
26,061
Rob Keniger
Author by

Rob Keniger

I'm a Mac and iOS developer. Currently working on interactive technology for museums and galleries at Art Processors.

Updated on July 09, 2022

Comments

  • Rob Keniger
    Rob Keniger almost 2 years

    I have a Core Data app with a fairly simple data model. I want to be able to store instances of NSImage in the persistent store as PNG Bitmap NSData objects, to save space.

    To this end, I wrote a simple NSValueTransformer to convert an NSImage to NSData in PNG bitmap format. I am registering the value transformer with this code in my App delegate:

    + (void)initialize
    {
        [NSValueTransformer setValueTransformer:[[PNGDataValueTransformer alloc] init] forName:@"PNGDataValueTransformer"];
    }
    

    In my data model, I have set the image attribute to be Transformable, and specified PNGDataValueTransformer as the value transformer name.

    However, my custom value transformer is not being used. I know this as I have placed log messages in my value transformer's -transformedValue: and -reverseTransformedValue methods which are not being logged, and the data that is being saved to disk is just an archived NSImage, not the PNG NSData object that it should be.

    Why is this not working?

    Here is the code of my value transformer:

    @implementation PNGDataValueTransformer
    
    + (Class)transformedValueClass
    {
        return [NSImage class];
    }
    
    + (BOOL)allowsReverseTransformation
    {
        return YES;
    }
    
    - (id)transformedValue:(id)value
    {
        if (value == nil) return nil;
        if(NSIsControllerMarker(value))
            return value;
        //check if the value is NSData
        if(![value isKindOfClass:[NSData class]])
        {
            [NSException raise:NSInternalInconsistencyException format:@"Value (%@) is not an NSData instance", [value class]];
        }
        return [[[NSImage alloc] initWithData:value] autorelease];
    }
    
    - (id)reverseTransformedValue:(id)value;
    {
        if (value == nil) return nil;
        if(NSIsControllerMarker(value))
            return value;
        //check if the value is an NSImage
        if(![value isKindOfClass:[NSImage class]])
        {
            [NSException raise:NSInternalInconsistencyException format:@"Value (%@) is not an NSImage instance", [value class]];
        }
        // convert the NSImage into a raster representation.
        NSBitmapImageRep* bitmap    = [NSBitmapImageRep imageRepWithData: [(NSImage*) value TIFFRepresentation]];
        // convert the bitmap raster representation into a PNG data stream
        NSDictionary* pngProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:NSImageInterlaced];
        // return the png encoded data
        NSData* pngData             = [bitmap representationUsingType:NSPNGFileType properties:pngProperties];
        return pngData;
    }
    
    @end
    
  • Rob Keniger
    Rob Keniger over 14 years
    Hi Brian, the +initialize method is in my app delegate, not the value transformer and I can definitely verify that it's being called. If I put a breakpoint in the -init method of my NSValueTransformer, it is definitely initialized. If I set the attribute to be of type "binary" and apply my value transformer to each binding in IB, it works fine. I just don't understand why the custom transformer is not being used when I specify the attribute type as Transformable.
  • Rob Keniger
    Rob Keniger over 14 years
    Thanks Brad. I tried this and it didn't make any difference, my value transformer is just ignored.
  • Rob Keniger
    Rob Keniger over 11 years
    Yes, I know this and I'm doing it.
  • Lukasz
    Lukasz over 11 years
    In my case it was exactly the problem. You may also check if transformer name matches the one you declared in data model and if you did not inverse bodies of the methods: transformedValue and reverseTransformedValue.
  • webmastx
    webmastx about 8 years
    Same here! Did you find a solution?