Sunday, April 17, 2011

Observing changes of derived properties: CALayer KVO example

Is there a way to observe changes in derived properties? For example, I want to know when a CALayer has been added as a sublayer so that I can adjust its geometry relative to its (new) parent.

So, I have a subclassed CALayer, say CustomLayer, and I figured I could register an observer for the property in init:

[self addObserver:self forKeyPath:@"superlayer" options:0 context:nil]

and implement observeValueForKeyPath:ofObject:change:context. Nothing ever happens because, presumably, superlayer is a derived property (the attr dictionary stores an opaque ID for the parent). Similarly, I can't subclass setSuperlayer: because it is never called. In fact, as far as I can tell there are no instance methods called or public properties set on the sublayer when a parent does [self addSublayer:aCustomLayer].

Then I thought, OK, I'll subclass addSublayer like this:

- (void)addSublayer:(CALayer *)aLayer {
    [aLayer willChangeValueForKey:@"superlayer"];
    [super addSublayer:aLayer];
    [aLayer didChangeValueForKey:@"superlayer"];
}

but still nothing! (Perhaps it's a clue that when I make a simple standalone test class and use the will[did]ChangeValueForKey: then it works.) This is maybe a more general Cocoa KVO question. What should I be doing? Thanks in advance!

From stackoverflow
  • [self addObserver:self forKeyPath:@"superlayer" options:0 context:nil]
    

    Don't observe yourself through KVO. Change your accessors instead.

    Similarly, I can't subclass setSuperlayer: because it is never called.

    I take it you tried this and added NSLog and found that it wasn't called?

    Then I thought, OK, I'll subclass addSublayer like this:

    - (void)addSublayer:(CALayer *)aLayer {
        [aLayer willChangeValueForKey:@"superlayer"];
        [super addSublayer:aLayer];
        [aLayer didChangeValueForKey:@"superlayer"];
    }
    

    And the parent layer is also a CustomLayer, right? If the parent layer is a plain CALayer, anything you do in CustomLayer will have no effect.

    dk : Re "don't observe yourself through KVO", in this case the property is derived so I can't change its accessor to trap the change. Re the 2nd part of the question: yes the parent layer is also a custom CA subclass. That's called. But the KVO notice isn't called. Weird.
  • Well, superlayer is defined as a readonly property, which means that there's no setSuperlayer: method. (If there is, it would be private, and you probably shouldn't use it.) If I had to make a guess, it would be that the superlayer property just isn't KVO-compliant. And, aside from that, I generally don't think it's a good idea for classes to observe themselves.

    Maybe there's another way of doing this. When a layer is added to a superlayer, the onOrderIn action takes place. Now, actionForKey: is an instance method that gives a layer an opportunity to customize the default animations for certain properties. You could override actionForKey: to detect when the onOrderIn action takes place, do your thing, then call super's implementation.

    I consider this a pretty messy hack, too, though. But it should be a bit more "self-contained" than having to use custom layers for everything and messing with KVO messages.

    dk : Ooh. I like the kCAOnOrderIn idea. Thanks for the tip. Will try. At least now I know from the 2 comments so far that I'm not doing something terribly wrong, but just struggling with the lack of completeness in an API implementation.

0 comments:

Post a Comment