CALayer: Where does the implicit animation comes from? - ios

I'm new to Core Animation. While learning about implicit animations, I came across a question.
I put a layer on the screen and made it color change to a random value when pressing a button, which triggers an implicit animation.
#define RANDOM_0_1 (arc4random() / (CGFloat)UINT_MAX)
- (IBAction)changeColor:(id)sender {
CGFloat r = RANDOM_0_1;
CGFloat g = RANDOM_0_1;
CGFloat b = RANDOM_0_1;
self.colorLayer.backgroundColor = [UIColor colorWithRed:r green:g blue:b alpha:1.0f].CGColor;
}
It works pretty well. Then I use
NSLog(#"%#", [self.colorLayer actionForKey:#"backgroundColor"]);
to get the animation object passed implicitly to the layer. I got
<CABasicAnimation: 0x7f8ae1d21970>.
Referring to the document, I learned that there are four way for a layer to get an action. 'Delegate, actions dictionary, style dictionary and +[CALayer defaultActionForKey:]' Then I stated wonder which step does the animation object really comes from. So I wrote this to check
NSLog(#"%# %# %#", self.colorLayer.delegate, self.colorLayer.actions, self.colorLayer.style);
It gave me three (null)
(null) (null) (null)
As the document said, these values should be set to nil by default.
So it must be +[CALayer defaultActionForKey:] that gives me the animation object. However, when I call
NSLog(#"%#", [self.colorLayer actionForKey:#"backgroundColor"]);
it still gave me a (null).
That's quite strange I thought. I started wonder if the 'key' passed into has been somehow changed by the internal implementation. So I referred to this post to print the argument passed to the method as well as the return value.
static id<CAAction> (*__originalCALayerDefaultActionForKey)( CALayer *, SEL, NSString *) ;
static id<CAAction> CALayerDefaultActionForKey( CALayer * self, SEL _cmd, NSString * event )
{
id res = (*__originalCALayerDefaultActionForKey)( self, _cmd, event );
NSLog(#"%#<%p> %# %#\n", [ self class ], self, event, res ) ;
return res;
}
I got the result like this
CALayer<0x106da5ef0> position (null)
CALayer<0x106da5ef0> bounds (null)
CALayer<0x106da5ef0> backgroundColor (null)
the 'key' passed into was exactly the property name, but it always returns null.
So, could anyone explain where does the animation object comes on earth, or provide me some technique to find out the answer?
Thanks. :)

The CALayer's SDK default animations are implemented in the .m, which you cannot get it in normal way.
And the SDK offers optional ways for user to use customised animation:
delegate:
You can use a delegate object to provide the layer’s contents, handle the layout of any sublayers, and provide custom actions in response to layer-related changes. The object you assign to this property should implement one or more of the methods of the CALayerDelegate informal protocol. ...
actions:
The default value of this property is nil. You can use this dictionary to store custom actions for your layer. The contents of this dictionary searched as part of the standard implementation of the actionForKey: method.
they're just for animation customisation.
e.g. you can create a custom CALayer subclass (say CustomLayer) which override the -actionForKey:
- (id<CAAction>)actionForKey:(NSString *)event
{
if ([event isEqualToString:#"strokeStart"] || [event isEqualToString:#"strokeEnd"]) {
CABasicAnimation * strokAnimation = [CABasicAnimation animationWithKeyPath:event];
strokAnimation.removedOnCompletion = NO;
strokAnimation.fillMode = kCAFillModeForwards;
strokAnimation.duration = .3f;
strokAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
strokAnimation.fromValue = [self.presentationLayer valueForKey:event];
return strokAnimation;
}
return [super actionForKey:event];
}
in this way, whenever you update strokStart & strokend for your CustomLayer instance, it'll invoke the strokAnimation you offered instead of the default animation that SDK implemented.
But style, it's for setting the animatable properties' value w/o animation (cause by default, whenever you update CALayer instance's animatable properties' value, it'll invoke default animation if you don't offer custom one):
An optional dictionary used to store property values that aren't explicitly defined by the layer.
If the style dictionary does not define a value for an attribute, the receiver’s defaultValueForKey: method is called. The default value of this property is nil.
e.g. like strokeStart & strokeEnd, their default values are 0.f & 1.f, you can modify style to use customized default value, like:
aLayer.style = #{#"strokeStart" : #.2f,
#"strokeEnd" : #.6f};
in this way, the layer's animation won't be invoked, like in the initialisation step, especially when you've offered a complex animation for the key, it'll helps a lot.
For +defaultActionForKey:, it's more like user defined default actions, as the doc said:
Returns a suitable action object for the given key or nil of no action object was associated with that key.
Classes that want to provide default actions can override this method and use it to return those actions.
You can override this method like -actionForKey:, below is the order to check whether the action exists when you modified the related property:
Check whether the delegate exists & delegate method -actionForLayer:forKey: is implemented;
Check whether the action of key was offered in -actionForKey:;
Check whether the action of key exists in actions while invoking [super actionForKey:event] after step 2;
Follows step 3 if no action of key exists in actions, check whether the action of key exists in +defaultActionForKey:.
Use SDK implemented default actions, which we cannot get (or might have unknown event name), as far as I know.
Generally, +defaultActionForKey: is like a global customised setting in class level for your CALayer subclass, while actions, -actionForLayer:forKey: & -actionForKey: to handle special layers.

Related

How to change the value of the member variables with runtime In Objective-C?

I have a tableView, I want its contentInsets is always zero. So I add the code :
class_replaceMethod([UITableView class], #selector(setContentInset:), (IMP)setContentInsetZero, "#");
then I code the setContentInsetsZero function:
void setContentInsetZero(id SELF, SEL _cmd, UIEdgeInsets insets) {
Ivar tableViewInsets = class_getInstanceVariable([UITableView class], "_contentInset");
object_setIvar(SELF, tableViewInsets, [NSValue valueWithUIEdgeInsets:UIEdgeInsetsZero]);
}
when the program is running , it called setContentInsetZero function, but it dose not work? Could anyone tell me why?
You can do what you're trying to do by taking the method swizzling one step further. Treat your new method like a wrapper instead of a replacement, it can intercept the parameter and call the original.
Take a look at this link for an exact way to do what you need: http://nshipster.com/method-swizzling/
This will affect all UITableViews in your application, a better solution might be subclassing UITableView and overriding the method (using a super call to pass the replaced parameter). Then you can just use this where it is needed.

What is the -(CGRect *)frame method in iOS?

Codes:
if (!CGRectContainsPoint([[viewArray objectAtIndex:0] frame], CGPointMake(newX, newY)) )
{
....
}
Question
I wanna to get a "frame" value of a UIView in a view array, so i used this:
((UIView *)[viewArray objectAtIndex:0]).frame
I utilize (UIView *) to compulsively convert an id object to UIView, otherwise it will trigger error like "Property 'frame' not found on object of type 'id'". But the method below seems successfully escape from this error:
[[viewArray objectAtIndex:0] frame]
So [id frame] can Automatically detect the id's real type and then make it to call the method?
I really wanna to know methods like
[id frame]----id's real type is UIView
[id view ]----id's real type is UIViewController
Are these methods getter methods or setter methods? I cannot tell the diffrence and the apple document show them like :
#property(nonatomic) CGRect frame
Thx a lot for your help!
The method called in the end is -(CGRect)frame;. Using the dot syntax is just a syntactic sugar the objective-c compiler provides. For this sugar to work, the compiler needs to know the type of the object it uses.
On the other end, objective-c uses message passing for calling methods. Which means that when you use the syntax [id message], it will try to execute the selector #selector(message) on id. id being a special type denoting any object in the runtime, it accepts every selector and will throw an unknown selector exception at runtime if the object cannot execute it.
By defining a property like: #property(nonatomic) CGRect frame the property works as both a getter and setter.
For example, to modify the width of a frame, you might write:
CGRect frame = myView.frame;
frame.size.width = 100;
myView.frame = frame;
For a good description of dot notation, see this fine answer.

Non-Object Attribute in Core Data, transient properties

Feel lost after reading this section: A Non-Object Attribute
According to the Basic-Approach also contained in above link, I should have 2 attributes in my custom-code when handling "transient properties":
1st attribute, for the actually-wanted (un-supported) custom type => transient attribute
2nd attribute, for shadow-representation (concrete supported) type => persistent attribute
......
My reading was very enjoyable, until reached "A Non-Object Attribute" section, which puzzle me deeply, as quoted below:
...When you implement the entity’s custom class, you typically add an instance variable for the attribute. ...
《 OK, I can follow this...make an iVar is no big deal》
If you use an instance variable to hold an attribute, you must also implement primitive get and set accessors
《 OK, I know how to do primitive-accessor. why need them? because internal-optimized-storage inside MO can be efficiently used, I guess.》
#interface MyManagedObject : NSManagedObject
{
 NSRect myBounds; // I assume this suppose to be the **transient attribute**
}
#property (nonatomic, assign) NSRect bounds; // I assume this is the **persistent attribute**
#property (nonatomic, assign) NSRect primitiveBounds; // because complier forces me to implement below primitive-accessors ?
#end
- (NSRect)primitiveBounds
{
return myBounds; // accessing iVAR storage for **transient attribute**? I hope so
}
- (void)setPrimitiveBounds:(NSRect)aRect
myBounds = aRect; // accessing iVAR storage for **transient attribute**? I hope so
}
From here down below, I have... too many ???????????? unsolved
- (NSRect)bounds
{
[self willAccessValueForKey:#"bounds"]; //KVO notice of access **persistent attribute**, I guess
NSRect aRect = bounds; //will this invoke primitive-Getter ???
[self didAccessValueForKey:#"bounds"];
if (aRect.size.width == 0) //bounds has not yet been unarchived, Apple explained
 {
NSString *boundsAsString = [self boundsAsString]; // unarchiving pseudo method, I guess
if (boundsAsString != nil) //if that value is not nil, transform it into the appropriate type and cache it...Apple explained.
{
bounds = NSRectFromString(boundsAsString); //will this invoke primitive-Setter???
}
}
return bounds;
}
I put my final question list here:
1, do I STILL need to have 2 attributes to handle NON-Object-Attribute, transient attribute and persistent attribute?
2, how can iVar "myBounds" be represented/connected with "#property bounds"? Is this "#property bounds" the modeled-property in a MOM?
3, what is the purpose of implementation of primitive-accessor here? for enforcing me write KVO (will...did...) methods pair? for transferring values (in and out) between iVar "myBounds"and "#property bounds"?
4, in this line of code
bounds = NSRectFromString(boundsAsString); //will this invoke primitive-Setter???
is primitive-Setter called OR public/standard-Setter gets called? Why?
In iOS, there are the very convenient NSStringFromCGRect and CGRectFromNSString functions. Why not just use those and store a string?
Your questions:
Yes, you need the 2 attributes, as explained in the documentation.
Yes, this is based on the managed object model. The primitiveX name for x is generated / interpreted automatically.
You need the primitive accessor methods here to make it KVC - which is not the case with primitives.

RACObserve(), RAC() - how to set a BOOL value based on an NSString

Being a ReactiveCocoa newbie, I'm hoping for some advice with this:
I'm trying to create a dynamic form that contains multiple Field objects parsed from an XML file. Each Field can have muliple validation rules that will run against the Field's NSString *value param.
For the RAC part of the question-
inside each Field object, I want to bind BOOL completed to a signal that checks the Field's *value param against an array of rules. So far I've gotten here with my thinking:
#implementation Field
self = [super init];
if (self) {
RAC(self, completed) = [RACObserve(self, value) filter:^BOOL(NSString *fieldValue) {
NSLog(#"%s::self.completed = %d\n", sel_getName(_cmd), self.completed); // trying to watch the values here, with no luck
NSLog(#"%s::fieldValue = %#\n", sel_getName(_cmd), fieldValue); // same here, I'd like to be able to view the `*value` here but so far no luck
return [self validateCurrentValue]; // currently this method just checks value.length > 5
}];
}
return self;
The *value param has already been bound to my view model (successfully) and it gets updated each time a textfield changes.
What I'm looking for is a basic example or best-practice, the code above crashes when run so I know I'm missing something fundamental.
Thanks all
-filter: is simply passing values from RACObserve(self, value) through unchanged, but only if the block returns YES. So that means you're trying to set completed to values of whatever type value is. That's Probably Bad®.
But the good news is that you're really close!
Instead of filtering, you want to transform. You want to take every value and map it to something other thing. Namely whether that value passes validation. To do that, we use -map::
RAC(self, completed) = [RACObserve(self, value) map:^(NSString *fieldValue) {
return #([self validateCurrentValue]);
}];

Problem setting property value on Custom Control Template Part

I have a custom control template that contains a Slider control.
I name that as a part in the class that implements the custom control:
[TemplatePart(Name = MapZoomSliderName, Type = typeof(Slider))]
In the OnApplyTemplate() override, I get the Slider:
MapZoomSlider = (Slider) GetTemplateChild("MapZoomSlider");
if (null != MapZoomSlider)
{
MapZoomSlider.ValueChanged +=new RoutedPropertyChangedEventHandler<double>(MapZoomSlider_ValueChanged);
MapZoomSlider.Value = InitSliderValue; // crash
_lastSliderValue = MapZoomSlider.Value;
}
When I try to set the Slider's Value property, the app crashes with "Object reference not set to an instance of an object."
Getting the slider's value works as expected.
What do I need to do to set the Slider's value at run time?
Thanks for any tips...
What is "InitSliderValue"? Maybe its the wrong value type? (Must be a double) Also, zero or negative may not be a valid value.
It appears the problem was in setting the ValueChanged handler before changing the Value property. The ValueChanged handler tries to manipulate other parts of app, parts that might not be ready yet.
If I set the value, then add the handler, it works as desired.
MapZoomSlider.Value = InitSliderValue; // all good
MapZoomSlider.ValueChanged +=new RoutedPropertyChangedEventHandler<double>(MapZoomSlider_ValueChanged);

Resources