Using a CATransition with the NSView animator proxy
Using a CATransition with the NSView animator proxy
- Subject: Using a CATransition with the NSView animator proxy
- From: Allan Odgaard <email@hidden>
- Date: Sun, 11 Nov 2018 15:09:17 +0700
I am unable to make custom boolean properties animatable (via
CATransition).
Below is the source for a custom NSView subclass with a boolean property
(`visible`).
It implements `defaultAnimationForKey:` to return a CATransition
instance which should be used when the property is changed (via the
animator proxy) as demonstrated in the `animateVisible` method.
Problem is that it doesn’t work. What happens is that when the
property is changed via the animator proxy, it shows no updates for the
duration of the animation, and then after the duration, it updates the
layer to the final state.
I would expect the animator proxy to retrieve the animation using
`animationForKey:`, add it to the view’s layer, and then update the
NSView property.
I implemented these steps in `manualAnimateVisible` and that *does*
work!
So what am I missing here?
Some further questions about animations:
1. NSView returns a CATransition set to `fade` for the `hidden`
property, but `someView.animator.hidden = YES` doesn’t fade out the
view for me. Is this supposed to work?
2. A few non-NSView classes conform to the NSAnimatablePropertyContainer
protocol, allowing animation when changing properties. Is there an easy
way for my custom controllers to also conform to this protocol?
Basically obtaining an animator proxy without having to write this from
scratch (it sort of seems the proxy should be reusable across classes,
so that subclasses can add additional properties without breaking the
properties of the superclass, but the NSView animator proxy and the
non-NSView animator proxies do appear to work differently, as the former
can assume a de facto layer, where the latter may have zero or more
layers to animate).
3. CAAnimation, CAMediaTiming, etc. seems to be data objects that are
added to a CALayer and then the CALayer has all the logic, at least
judging from the public API. But I see that NSLayoutConstraint also
conforms to NSAnimatablePropertyContainer. This means it must be using
CAAnimation objects, but it has no layers. So how is it able to use
these objects without any layers? Seems it would either be through dummy
layers (wasteful) or private API (which would surprise me based on how
old the protocol is).
4. It is possible to call methods via the animator proxy and side
effects of these are animated. For example replacing a view using
`[view.superview.animator replaceSubview:view with:[[NSView alloc]
initWithFrame:view.frame]]` will run the animation for the superview’s
`subviews` key, even `[view.animator removeFromSuperview]` will use the
animation for this key (in the superview). How is this implemented?
Side effects do seem limited to standard properties, i.e. my custom
(animatable) properties does not work like this (even though they work
when set explicitly via the animator proxy). This makes me think that
NSView actually tests if there is an active animator proxy, but how to
do such check? The
NSAnimationContext.currentContext.allowsImplicitAnimation property is NO
even in methods called via the animator proxy.
- - -
The sample code for my custom view below.
While searching for answers to my problem I also came across this GitHub
project showing the same issue https://github.com/jjk/TestTransitions
(linked to from an unanswered Stack Overflow question).
@interface MyImageView : NSView
@property (nonatomic, getter = isVisible) BOOL visible;
@end
@implementation MyImageView
+ (id)defaultAnimationForKey:(NSAnimatablePropertyKey)key
{
if([key isEqualToString:@"visible"])
{
CATransition* transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionEaseOut];
transition.duration = 1;
return transition;
}
return [super defaultAnimationForKey:key];
}
- (instancetype)initWithFrame:(NSRect)frameRect
{
if(self = [super initWithFrame:frameRect])
{
self.wantsLayer = YES;
self.layer = [CALayer layer];
}
return self;
}
- (void)setVisible:(BOOL)flag
{
if(_visible == flag)
return;
_visible = flag;
self.layer.contents = flag ? [NSImage
imageNamed:NSImageNameMultipleDocuments] : nil;
}
- (void)animateVisible
{
self.animator.visible = YES;
}
- (void)manualAnimateVisible
{
if(id anim = [self animationForKey:@"visible"])
[self.layer addAnimation:anim forKey:@"visible"];
self.visible = YES;
}
@end
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden