Search code examples
iphonequartz-graphicsopacitykey-value-observingkey-value-coding

Passing CGColorRef as a value in KVC/KVO


I'm using Opacity to generate all my Quartz2D artwork and I'm now tackling the issue of changing colours via KVC/KVO. Opacity defines all its colour variables as @dynamic and implements its own accessors as part of the class definition.

My question is; how do I pass a new CGColorRef value by key value?

So far (as a testbed) I've got this far:

// Testbed ... Testbed ... Testbed ... Testbed ... Testbed ... Testbed ... Testbed ...
      CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
      CGFloat components[4] = {1.0f, 1.0f, 1.0f, 1.0f};
      CGColorRef color = CGColorCreate(space, components);

      [myCALayerReceiverObject setValue: color forKey: @"numColour"];
// Testbed ... Testbed ... Testbed ... Testbed ... Testbed ... Testbed ... Testbed ...

The colour space and creation code is lifted directly from Opacity's CALayer class generator (I'm not that advanced yet) but passing 'color' as the value causes the iPhone simulator to crash and XCode gives me a very cryptic warning;

"Incompatible pointer types sending 'CGColorRef' (aka struct 'CGColor *') to parameter of type 'id' ".

How do I wrap up the CGColorRef to pass it across KVC to the receiver? Thanks in advance.


Solution

  • I was going to suggest using NSValue to wrap your color object, but during testing I have come to the conclusion that KVO/KVC will not work with CGColors. According to the Key-Value Coding Programming Guide, primitive types and structures are wrapped using NSValue or NSNumber. However, when trying to access a CGColorRef property, I get a "not KVC compliant" exception. It seems that pointers to structures are not wrapped to NSValues in the same way as the structures themselves. Here is my test code:

    @interface MyClass : NSObject {
        CGColorRef color;
    }
    @property (assign) CGColorRef color;
    @end
    
    @implementation MyClass
    - (CGColorRef)color { return color; }
    - (void)setColor:(CGColorRef)newColor { color = newColor; }
    @end
    
    int main(int argc, char **argv) {
        NSAutoreleasePool *p = [NSAutoreleasePool new];
        MyClass *theObject = [MyClass new];
        CGColorRef theColor = CGColorCreateGenericRGB(1.0,1.0,1.0,1.0);
        theObject.color = theColor; // works
        NSLog(@"%@",[theObject valueForKey:@"color"]); // NSValue expected, actually causes exception
        [p release];
        return 0;
    }
    

    One possible workaround is to add a category to the proper class which defines accessors for a different key. These accessors would be NSValue wrappers for the numColour key.

    // in the category
    - (NSValue *)numColourKVC {
        return [NSValue valueWithPointer:(void*)self.numColour];
    }
    - (void)setNumColourKVC:(NSValue *)newValue {
        self.numColour = (CGColorRef)[newValue pointerValue];
    }
    

    Then you would set the color in your testbed as follows:

    [myCALayerReceiverObject setValue:[NSValue valueWithPointer:(void*)color] forKey:@"numColourKVC"];