Animating NSView property of custom structure type - objective-c

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.

Related

Accessing a property of a node

I currently have a SpriteKit game with the objective of shooting down enemies. I've implemented collision detection for it, and it works just fine. But I need to implement health for enemies. Enemies are constantly generated and keep moving, so you never know what node that should bebeSo I tried to declare my custom class node in didBeginContact method, then assigning it to bodyA, then changing it's health value, but this seems useless since I just create a new node (same shows the NSLog). I tried typecasting the declaration, but still with no luck. Did some research on this topic, but didn't find anything that suits me. Currently I can't provide source code for what I did, but I hope what I have requested is possible to explain. Please push me in the right direction.
Every SKSpriteNode has a userData NSMutableDictionary which can be used to store data (objects).
You first have initialize the dictionary like this:
myNode.userData = [NSMutableDictionary dictionary];
Then you can assign data to it like this:
float myHealth = 100.0;
NSString *myX = [NSString stringWithFormat:#"%f",myHealth];
[myNode.userData setValue:myX forKey:#"health"];
To read data you do this:
float myHealth = [[myNode.userData objectForKey:#"health"] floatValue];
I used float in my example but you can use whatever you want. Just remember that you cannot store primitives like float, int, long, etc... directly. Those need to be converted to NSNumber, NSString and so on.
That being said, Stephen J is right with his suggestion. You should subclass SKSpriteNode for your enemies and have health as a class property. Subclassing is much easier to work with in the long run and gives you greater flexibility compared to using the userData.
To illustrate some Object oriented concepts Stephen J and sangony are referring to, I have added some code for you.
Subclassing SKNode will define a new object class which inherits all functionality from SKNode. The main advantage here is that you can implement custom properties (such as health) and custom logic (such as lowering that health).
#interface EnemyNode : SKSpriteNode
- (void)getHit;
- (BOOL)isDead;
#property (nonatomic) CGFloat health;
#end
#implementation EnemyNode
- (instancetype)initWithColor:(UIColor *)color size:(CGSize)size {
self = [super initWithColor:color size:size];
if (self) {
self.health = 100.f;
}
}
- (void)getHit {
self.health -= 25.f;
}
- (BOOL)isDead {
return self.health <= 0;
}
#end
In your scene, you would use it as such:
EnemyNode *newEnemy = [[EnemyNode alloc] initWithColor:[UIColor blueColor] size:CGSizeMake(50,50)];
[self addChild:newEnemy];
...
[newEnemy getHit];
if ([newEnemy isDead]) {
[newEnemy removeFromParent];
}
For further illustration, you could take a look at my answer to a similar question.

Set NSButton enabled based on NSArrayController selection

OK, so I've set up an NSTableView bound to NSArrayController.
Now, I also have an NSButton which I want to make it "enabled" when there is a selection, and disabled when there is nothing selected.
So, I'm bind NSButton's Enabled to the Array Controller's selection with a value transformer of NSIsNotNil.
However, it doesn't seem to be working.
Am I missing anything?
Regardless of whether or not anything is selected, the selection property of NSArrayController returns an object (_NSControllerProxyObject). This is why your binding isn't working the way you expect, because selection will never be nil. Instead, I bind to selectionIndexes, rather than selection, and have a value transformer called SelectionIndexesCountIsZero implemented like so:
#interface SelectionIndexesCountIsZero : NSValueTransformer
#end
#implementation SelectionIndexesCountIsZero
+ (Class)transformedValueClass { return [NSNumber class]; }
+ (BOOL)allowsReverseTransformation { return NO; }
- (id)transformedValue:(NSIndexSet *)value { return [NSNumber numberWithBool:[value count] > 0]; }
#end
Incidentally, you can still bind to selection if you wish, but it will require a custom value transformer. Apple state that: If a value requested from the selection proxy [object] using key-value coding returns multiple objects, the controller has no selection, or the proxy is not key-value coding compliant for the requested key, the appropriate marker is returned. In other words, to find out if there is in fact no selection, you need to (i) get access to the proxy object, (ii) call one of the methods of your actual objects, and (iii) test to see if the return value from (ii) is NSNoSelectionMarker. Doing it this way the key method of your value transformer would look like this:
- (id)transformedValue:(id)selectionProxyObject {
// Assume the objects in my table are Team objects, with a 'name' property
return [selectionProxyObject valueForKeyPath:#"name"] == NSNoSelectionMarker ? #YES : #NO;
}
selectionIndexes is the better way since it is completely generic. In fact, if you do this sort of thing a lot it can be a good idea to build up a transformer library, which you can then just import into any project. Here are the names of some of the transformers in my library:
SelectionIndexesCountIsZero
SelectionIndexesCountIsExactlyOne
SelectionIndexesCountIsOneOrGreater
SelectionIndexesCountIsGreaterThanOne
// ...you get the picture
I bind to selectedObjects.#count, rather than selection

Non-animatable properties in subclasses of CALAyer

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.

Objective-C pattern for class instance variables?

What would be a nice pattern in Objective-C for class variables that can be "overridden" by subclasses?
Regular Class variables are usually simulated in Objective-C using a file-local static variables together with exposed accessors defined as Class methods.
However, this, as any Class variables, means the value is shared between the class and all its subclasses. Sometimes, it's interesting for the subclass to change the value for itself only. This is typically the case when Class variables are used for configuration.
Here is an example: in some iOS App, I have many objects of a given common abstract superclass (Annotation) that come in a number of concrete variations (subclasses). All annotations are represented graphically with a label, and the label color must reflect the specific kind (subclass) of its annotation. So all Foo annotations must have a green label, and all Bar annotations must have a blue label. Storing the label color in each instance would be wasteful (and in reality, perhaps impossible as I have many objects, and actual configuration data - common to each instance - is far larger than a single color).
At runtime, the user could decide that all Foo annotations now will have a red label. And so on.
Since in Objective-C, Classes are actual objects, this calls for storing the Foo label color in the Foo class object. But is that even possible? What would be a good pattern for this kind of things? Of course, it's possible to define some sort of global dictionary mapping the class to its configuration value, but that would be kind of ugly.
Of course, it's possible to define some sort of global dictionary mapping the class to its configuration value, but that would be kind of ugly.
Why do you think this would be ugly? It is a very simple approach since you can use [self className] as the key in the dictionary. It is also easy to make it persistent since you can simply store the dictionary in NSUserDefaults (as long as it contains only property-list objects). You could also have each class default to its superclass's values by calling the superclass method until you find a class with a value.
+ (id)classConfigurationForKey:(NSString *)key {
if(_configurationDict == nil) [self loadConfigurations]; // Gets stored values
Class c = [self class];
id value = nil;
while(value == nil) {
NSDictionary *classConfig = [_configurationDict objectForKey:[c className]];
if(classConfig) {
value = [classConfig objectForKey:key];
}
c = [c superclass];
}
return value;
}
+ (void)setClassConfiguration:(id)value forKey:(NSString *)key {
if(_configurationDict == nil) [self loadConfigurations]; // Gets stored values
NSMutableDictionary *classConfig = [_configurationDict objectForKey:[self className]];
if(classConfig == nil) {
classConfig = [NSMutableDictionary dictionary];
[_configurationDict setObject:classConfig forKey:[self className]];
}
[classConfig setObject:value forKey:key];
}
This implementation provides no checking to make sure you don't go over the top superclass, so you will need to ensure that there is a value for that class to avoid an infinite loop.
If you want to store objects which can't be stored in a property list, you can use a method to convert back and forth when you access the dictionary. Here is an example for accessing the labelColor property, which is a UIColor object.
+ (UIColor *)classLabelColor {
NSData *data = [self classConfigurationForKey:#"labelColor"];
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
+ (void)setClassLabelColor:(UIColor *)color {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:color];
[self setClassConfiguration:data forKey:#"labelColor"];
}
my answer here may help:
What is the recommended method of styling an iOS app?
in that case, your annotation just holds a reference to a style (e.g. you need only one per style), and the size of a pointer for an entire style is not bad. either way, that post may give you some ideas.
Update
Jean-Denis Muys: That addresses the sample use case of my question, but not my question itself (a pattern to simulate class instance variables).
you're right, i didn't know how closely your example modeled your problem and i considered commenting on that.
for a more general and reusable solution, i'd probably just write a threadsafe global dictionary if your global data is nontrivial (as you mentioned in your OP). you could either populate it in +initialize or lazily by introducing a class method. then you could add a few categories to NSObject to access and mutate the static data -- do this for syntactical ease.
i suppose the good thing about that approach is that you can reuse it in any program (even though it may appear ugly or complex to write). if that's too much locking, then you may want to divide dictionaries by prefixes or create a simple thread safe dictionary which your class holds a reference to -- you can then synthesize an instance variable via the objc runtime to store it and declare an instance method to access it. the class method would still have to use the global data interface directly.

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).