Non-animatable properties in subclasses of CALAyer - objective-c

I have defined a subclass of CALayer with an animatable property as discussed here. I would now like to add another (non-animatable) property to that layer to support its internal bookkeeping.
I set the value of the new property in drawInContext: but what I find that it is always reset to 0 when the next call is made. Is this so because Core Animation assumes that this property is also for animation, and that it "animates" its value at constant 0 lacking further instructions? In any case, how can I add truly non-animatable properties to subclasses of CALayer?
I have found a preliminary workaround, which is using a global CGFloat _property instead of #property (assign) CGFloat property but would prefer to use normal property syntax.
UPDATE 1
This is how I try to define the property in MyLayer.m:
#interface MyLayer()
#property (assign) CGFloat property;
#end
And this is how I assign a value to it at the end of drawInContext::
self.property = nonZero;
The property is e.g. read at the start of drawInContext: like so:
NSLog(#"property=%f", self.property);
UPDATE 2
Maybe this it was causes the problem (code inherited from this sample)?
- (id)actionForKey:(NSString *) aKey {
if ([aKey isEqualToString:#"someAnimatableProperty"]) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:aKey];
animation.fromValue = [self.presentationLayer valueForKey:aKey];
return animation;
}
return [super actionForKey:aKey]; // also applies to my "property"
}

To access your standard property from within the drawing method, during an animation, you need to make a few modifications.
Implement initializer
When CoreAnimation performs your animation, it creates shadow copies of your layer, and each copy will be rendered in a different frame. To create such copies, it calls -initWithLayer:.
From Apple's documentation:
If you are implementing a custom layer subclass, you can override this method and use it to copy the values of instance variables into the new object. Subclasses should always invoke the superclass implementation.
Therefore, you need to implement -initWithLayer: and use it to copy manually the value of your property on the new instance, like this:
- (id)initWithLayer:(id)layer
{
if ((self = [super initWithLayer:layer])) {
// Check if it's the right class before casting
if ([layer isKindOfClass:[MyCustomLayer class]]) {
// Copy the value of "myProperty" over from the other layer
self.myProperty = ((MyCustomLayer *)layer).myProperty;
}
}
return self;
}
Access properties through model layer
The copy, anyway, takes place before the animation starts: you can see this by adding a NSLog call to -initWithLayer:. So as far as CoreAnimation knows, your property will always be zero. Moreover, the copies it creates are readonly, if you try to set self.myProperty from within -drawInContext:, when the method is called on one of the presentation copies, you get an exception:
*** Terminating app due to uncaught exception 'CALayerReadOnly', reason:
'attempting to modify read-only layer <MyLayer: 0x8e94010>' ***
Instead of setting self.myProperty, you should write
self.modelLayer.myProperty = 42.0f
as modelLayer will instead refer to the original MyCustomLayer instance, and all the presentation copies share the same model. Note that you must do this also when you read the variable, not only when you set it. For completeness, one should mention as well the property presentationLayer, that instead returns the current (copy of the) layer being displayed.

Related

Animating NSView property of custom structure type

I have an NSView subclass with a property declared like this in the header:
#property (nonatomic) BaseRange range;
BaseRange is defined as:
typedef struct BaseRange {
float start;
float len;
} BaseRange;
I want to animate the range property using the NSAnimatablePropertyContainer protocol.
My class overrides + defaultAnimationForKey: as required.
The problem is that when I call [myView.animator setRange:<some value>] (myView being a instance of the class in question), myView is sent -setNilValueForKey: for the range key at every step of the animation, except for the final value. IOW, the animation doesn't work.
If I define the range property like so:
#property (nonatomic) NSSize range;
and don't change anything else, no -setNilValueForKey: message is sent, but rather intermediate values for the range key, as normal.
But I don't want to use NSSize, because the range key should represent a range rather than a a size.
Any suggestions?
Provided you return CAPropertyAnimation or its subclass from the +[NSAnimatablePropertyContainer defaultAnimationForKey:] method, the behaviour is expect, as it works with strict set of values:
integers and doubles
CGRect, CGPoint, CGSize, and CGAffineTransform structures
CATransform3D data structures
CGColor and CGImage references
To my knowledge CAAnimation and its subclasses cannot work with arbitrary values beyond this set and focused primarily to work with Core Graphics properties (layers, frames, colors, etc..). On macOS, however, you can use NSAnimation class instead, which is much more flexible but requires additional customisation to your class. First you should subclass NSAnimation itself and override the -[NSAnimation setCurrentProgress:] method (this is not mandatory, but otherwise you won't be able to get smooth enough transition between animation steps):
- (void)setCurrentProgress:(NSAnimationProgress)currentProgress {
[super setCurrentProgress:currentProgress];
// Range owner refers to the object (view) with the property of custom struct type
// Range Key Path helps to locate the property inside the object
if (!_rangeOwner || !_rangeKeyPath) {
return;
}
static const char *const kTDWRangeEncoding = #encode(TDWRange);
// Wraps new range with NSValue
NSValue *newRange = [NSValue value:&(TDWRange){
.location = (_targetRange.location - _originalRange.location) * currentProgress + _originalRange.location,
.length = (_targetRange.length - _originalRange.length) * currentProgress + _originalRange.length
} withObjCType:kTDWRangeEncoding];
// Sends new value to the object that owns the range property
[_rangeOwner setValue:newRange
forKeyPath:_rangeKeyPath];
}
In this implementation:
TDWRange refers to the custom structure which represents the range;
_rangeOwner refers to the object which has a property of type TDWRange;
_rangeKeyPath refers to the key path by which the NSAnimation subclass can find the property;
_targetRange is the value towards which the animation interpolates;
_originalRange is the value of the property before animation gets started.
Then, in your custom view class you should provide a separate means to update a property with the given animation. Provided the animation class is called TDWRangeAnimation and the range property is reachable through #"range" key path, such a method may look like this:
- (void)setRange:(TDWRange)range animated:(BOOL)animated {
if (animated) {
TDWRangeAnimation *rangeAnimation = [[TDWRangeAnimation alloc] initWithRangeOwnder:self
rangeKeyPath:#"range"
targetRange:range
duration:0.4
animationCurve:NSAnimationEaseOut];
rangeAnimation.animationBlockingMode = NSAnimationNonblocking;
[rangeAnimation startAnimation];
} else {
self.range = range;
}
}
You are not required to retain the animation object, since it's maintained by the run loop until the animation finishes.
P.S. Feel free to refer to the gist in case you need a complete implementation sample.

Is there a way to turn a weak reference into a strong one?

I have an object that is set as the delegate of another object, whose delegate property is weak.
- (YYService *)service
{
XXHandler *handler = [[XXHandler alloc] init];
// YYService's "delegate" property is weak
return [[YYService alloc] initWithDelegate:handler];
// The XXHandler is deallocated because there are no strong references to it
}
Since nothing else references the delegate it ends up getting deallocated, but I want it to live for as long as the parent object does as if the parent had a strong reference to its delegate. Is there a simple way to accomplish this?
The easy why to "solve" that problem is to subclass YYService, giving the subclass an additional strong property and set that one in -initWithDelegate:.
But this "solution" would deepen a problem in your design instead of solving that.
Let's have a look, why delegates are usually hold weakly:
The delegating class has a general – or no – behavior which might not fit in the class' user's case, i. e. if something happens. (An operation completes, an error occurs, $whatever) So the delegating class gives you the opportunity to customize the behavior including running custom code. Delegating is in competition with subclassing, but in difference to subclassing is on a per instance basis (instead of a per class basis) and at run time (instead of compile time).
Because it works on per instance basis, the instance creating the delegate typically holds the delegating instance strongly. This code knows the customization that should apply to the delegating instance:
-(void)createDelegate
{
self.delegating = [Delegating new]; // I create and hold the instance strongly
delegating.delegate = self; // I customize it
}
Then the delegating instance cannot hold the delegate strongly, because this would be a retain cycle.
In your snippet that does not work, because -service returns the newly created delegating instance. And even it would be possible to return both instances, I wouldn't like it, because creating the delegating object and installing the delegate would be a two-step operation, even it is semantically a one-stepper. So If you do not have self as the delegate, you should do the whole installation process in one method:
-(void)installService
{
self.handler = [[XXHandler alloc] init]; // Hold the handler strongly
self.service = [[YYService alloc] initWithDelegate:handler];
}
If you do not know the concrete instance object acting as delegate, pass it as argument:
-(void)installServiceWithDelegate:(id)delegate
{
self.delegate = delegate;
self.service = [[YYService alloc] initWithDelegate:delegate];
}
…
[self installServiceWithDelegate:[YourConcreteClass new]];
But you should not try to turn things upside down or inside out.

Call a method every time a parameter is set on Objective-C (Cocoa)

I currently have a class with 15 properties (and growing), and I'm finding myself having to call an update method every time one of those properties change.
Currently, I'm overriding every setter with a code like this:
-(void)setParameterName:(NSUInteger)newValue {
if (_param == newValue)
return;
_param = newValue;
[self redraw];
}
The method [self redraw]; being the key here.
Is there a better way to do it? Should I be using keyValue observers (the method observeValue:forKeyPath:ofObject:change:context:)?
Notes:
All properties (so far) are assign (mostly enum, NSUInteger, CGFloat and BOOL);
All those properties are set using bindings (method bind:toObject:withKeyPath:options:). Except when loading from the filesystem (which is not important, as I already call the drawing methods on every object after the loading is done);
The value changes are only for the current object. I do not need to be told when changes occur on other objects;
I have other properties that I don't need to watch the changes on it (because it will have no effect on my output and drawing the output is kinda time-consuming).
Thanks!
Since these properties are updated using bindings, which invoke -setValue:forKey:, you can override that method instead of writing custom setters:
+ (NSArray *) keysAffectingDrawing {
static NSArray *singleton;
if (!singleton)
singleton = [NSArray arrayWithObjects:
#"property1",
#"property2",
#"property3",
nil];
return singleton;
}
- (void) setValue:(id) value forKey:(NSString *) key {
[super setValue:value forKey:key];
if ([[CustomClass keysAffectingDrawing] containsObject:key]) [self redraw];
}
(I was first inclined recommend key-value observing but agree it's not the best solution here. I think the reason is in part that there's only one object, and in part because the design doesn't follow MVC. Usually in MVC an object that draws itself isn't the one with all the properties.)
(Added: Ahh, I see. The model is responsible for rendering the properties to a bitmap, and that's what -redraw does. That's fine MVC. To make it clearer, I recommend changing the name of the method from -redraw to something like -updateImage or -renderImage, since it doesn't actually do any drawing.)
You could use the Key-Value Observing to avoid repeating in all properties setter the method call, however i think that calling the method directly in the setter is not the wrong way to do it, and could even be faster ...

How does one know when it's safe to use a parent method in NS subclasses?

As an example, when I'm using an NSMutableDictionary, I know it inherits all the methods of NSDictionary, but how can I know/trust that it has overridden the behavior of those methods if I want to use NSDictionary methods (such as +dictionaryWithObjectsAndKeys) to create my mutable dictionary instance?
More generally, is it the Framework's responsibility to make sure subclasses don't blindly inherit methods that can potentially break instances of the subclass if used? Or is it the coder's responsibility to know not to use them? If Square inherits from Rectangle, and via inheritance I can call
Square *newSquare = [[Square alloc] init];
[newSquare setWidth:3 andHeight:6]; //instead of -(void)setSide:(int)side
I've "broken" the square and other methods which depend on width being equal to height will not work now. What are the rules of the game?
The rule would be only expose what you would allow to be override it means, put on your interface what is really public. When necessary explicitly state that when overriding an specific method call at some point [super methodName].
On your example you would override the method - (void)setWidth:(int)width andHeight:(int)height, and you would like to throw an error if width != height. Or you could also throw an error and force the user to only use - (void)setSide:(int)side.
For example you could do:
// If you want to test and accept cases when width == height
- (void)setWidth:(int)width andHeight:(int)height {
NSAssert(width == height, NSLocalizedString(#"This is a Square. Width has to be height.", nil));
[super setWidth:width andHeight:height];
// Or
[self setSide:width];
}
// Or if you want to completely prohibit the usage of the method
- (void)setWidth:(int)width andHeight:(int)height {
NSAssert(NO, NSLocalizedString(#"This is a Square! Please use - (void)setSide:(int)side method.", nil));
}
If you would like to throw some errors and warnings at compilation time, you could use on the declaration of your methods, some of the macros defined on NSObjCRuntime.h.
I wouldn't trust the parent convenience method to call your inheriting init method. For example, that dictionary method could be defined as:
+ (id)dictionaryWithObjectsAndKeys:...
{
return [[[NSDictionary alloc] initWithObjectsAndKeys:...] autorelease];
}
If that method is defined that way then it won't be even be aware of your implementation.
You'd have to create your own convenience method. Something like would be in your MyDictionary implementation:
+ (id)myDictionaryWithObjectsAndKeys:...
{
return [[[MyDictionary alloc] initWithObjectsAndKeys:...] autorelease];
}
--
Also...
You probably should inherit Rectangle from Square. Inheritance is additive. You can describe Square with one size (width), but for Rectangle you have two sizes (width, height).

NSWindowController and isWindowLoaded

I have an NSWindowController and I initialize it like this;
+ (MyWindowController *) sharedController
{
static MyWindowController *singleton = nil;
if (!singleton) singleton = [[self alloc] initWithWindowNibName: #"myWindow"];
return singleton;
}
and I show windows like this;
[[MyWindowController sharedController] showWindow: nil];
Now the problem is that I need information from some controls on that window. But I do not want to load the window if it's not yet loaded because then I can just go with the defaults. Should I use isWindowLoaded? #property to access the singleton? or what is recommended here? (If #property, then please give me the readonly, nonatomic attributes too.)
Don't store model data in views. Have the controller (probably not MyWindowController, but the one that needs the data) own the real data (if any) and fill in any defaults.
Any values you fill in in Interface Builder should be for nothing more than sizing.
For example, if I know a field must hold a number whose value is ±50000, I'll enter “-50000” and size the field accordingly, and leave the “-50000” there. The actual default is more likely to be 0 or something, and I will have that provided by the controller that owns the value (or, if the field shows a property of a model object, I'll have the default provided by each new model object).