Search code examples
cocoacore-animationquartz-graphics

Redrawing custom CALayer subclass on custom property change


I am trying to build a special layer which draws text. This TWFlapLayer has an attributed string as a property:

TWFlapLayer.h:

@interface TWFlapLayer : CALayer
@property(nonatomic, strong) __attribute__((NSObject)) CFAttributedStringRef attrString;
@end

and gets synthesized in TWFlapLayer.m:

@implementation TWFlapLayer

@synthesize attrString = _attrString;

/* overwrite method to redraw the layer if the string changed */

+ (BOOL)needsDisplayForKey:(NSString *)key
{
    if ([key isEqualToString:@"attrString"]){
        return YES;
    } else {
        return NO;
    }
}

- (void)drawInContext:(CGContextRef)ctx
{
    NSLog(@"%s: %@",__FUNCTION__,self.attrString);
    if (self.attrString == NULL) return;
    /* my custom drawing code */
}

My intention was that the layer is automatically redrawn using my custom drawing method if the attrString property was changed using the synthesized setter method. However from the NSLog statement placed in the drawInContext: method I see that the layer is not redrawn.

By placing a breakpoint in the needsDisplayForKey method I made sure that it returns YES when asked for the attrString key.

I am now changing the attrString like this

// self.frontString is a NSAttributedString* that is why I need the toll-free bridging
self.frontLayer.attrString = (__bridge CFAttributedStringRef) self.frontString;

//should not be necessary, but without it the drawInContext method is not called
[self.frontLayer setNeedsDisplay]; // <-- why is this still needed?

I looked up the class method definition for needsDisplayForKey in the CALayer header file, but it seems to me that this is the method I would like to use or am I missing an important point here?

from CALayer.h:

/* Method for subclasses to override. Returning true for a given
 * property causes the layer's contents to be redrawn when the property
 * is changed (including when changed by an animation attached to the
 * layer). The default implementation returns NO. Subclasses should
 * call super for properties defined by the superclass. (For example,
 * do not try to return YES for properties implemented by CALayer,
 * doing will have undefined results.) */

+ (BOOL)needsDisplayForKey:(NSString *)key;

Summary

Why is my layer not redrawing when the custom property attrString is changed and marked by needsDisplayForKey: ?


Solution

  • CALayer.h also says:

    /* CALayer implements the standard NSKeyValueCoding protocol for all
     * Objective C properties defined by the class and its subclasses. It
     * dynamically implements missing accessor methods for properties
     * declared by subclasses.
    

    Apparently the needsDisplayForKey: mechanism relies on CALayer's dynamically-implemented accessor methods. So, change this:

    @synthesize attrString = _attrString;
    

    to

    @dynamic attrString;