How can I animate a UIColor in a CALayer?

2019-08-31 05:18发布

I have a custom CALayer within which I'm trying to enable the animation of certain properties using actionForKey and following this tutorial.

I have a CGFloat property that will change perfectly when inside an animation block but my other property, a UIColor, will not.

Here's my function:

- (id<CAAction>)actionForKey:(NSString *)event {

    if ([self presentationLayer] != nil && [[self class] isCustomAnimationKey:event]) {
        id animation = [super actionForKey:@"backgroundColor"];
        if (animation == nil || [animation isEqual:[NSNull null]]) {
            [self setNeedsDisplay];
            return [NSNull null];
        }
        [animation setKeyPath:event];
        [animation setFromValue:[self.presentationLayer valueForKey:event]];
        [animation setToValue:nil];
        return animation;
    }
    return [super actionForKey:event];
}

The colour is being set using CGContextSetFillColorWithColor but I can see from logs that the colour simply changes from one to the next without any of the interpolated values.

Any ideas?

2条回答
Summer. ? 凉城
2楼-- · 2019-08-31 05:39

This is my code and not an answer. There are three classes: OCLayer, OCView and OCViewController. You can see the value of "percent" is changing during animation while the value of "myColor" won't.

@interface OCLayer : CALayer
@property (nonatomic) CGFloat  percent;
@property (nonatomic) CGColorRef myColor;
@end


#import "OCLayer.h"
@implementation OCLayer
@dynamic percent;
@dynamic myColor;

- (id<CAAction>)actionForKey:(NSString *)key
{
if ([[self class] isCustomAnimKey:key])
{
    id animation = [super actionForKey:@"backgroundColor"];
    if (animation == nil || [animation isEqual:[NSNull null]])
    {
        [self setNeedsDisplay];
        return [NSNull null];
    }
    [animation setKeyPath:key];
    [animation setFromValue:   [self.presentationLayer valueForKey:key]];
        [animation setToValue : nil];
    return animation;
}
return [super actionForKey:key];
}


- (id)initWithLayer:(id)layer
{
self = [super initWithLayer:layer];
if (self)
{
    if ([layer isKindOfClass:[OCLayer class]])
    {
        self.percent = ((OCLayer *)layer).percent;
    }
}
return self;
}

+ (BOOL)needsDisplayForKey:(NSString *)key
{
 if ([self isCustomAnimKey:key]) return true;
 return [super needsDisplayForKey:key];
}

+ (BOOL)isCustomAnimKey:(NSString *)key
 {
  return [key isEqualToString:@"percent"] || [key isEqualToString:@"myColor"];
 }
 @end


@interface OCView : UIView
@property (weak, nonatomic) IBOutlet UIView *percentView;
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (nonatomic, strong) UIColor * myColor;

//- (UIColor*)myColor ;
//- (void)setMyColor:(UIColor *)color;
- (CGFloat )percent;
- (void)setPercent:(CGFloat )percent;
@end


    #import "OCView.h"
    #import "OCLayer.h"

    @implementation OCView

    - (void)displayLayer:(CALayer *)layer
    {
        CGFloat percent = [(OCLayer *)[self.layer presentationLayer] percent];

        CGColorRef myColor = [(OCLayer *)[self.layer presentationLayer] myColor];
        NSLog(@"%f", percent);
        NSLog(@"%@", myColor);

        self.percentView.backgroundColor = [[UIColor alloc]initWithCGColor: myColor];
        self.label.text = [NSString stringWithFormat:@"%.0f", floorf(percent)];
    }

    + (Class)layerClass
    {
        return [OCLayer class];
    }

    - (void)setPercent:( CGFloat )percent
    {
        ((OCLayer *)self.layer).percent = percent;
    }

    - (CGFloat )percent
    {
        return ((OCLayer *)self.layer).percent;
    }


    - (void)setMyColor:(UIColor *)color {
        ((OCLayer *)self.layer).myColor = color.CGColor;
    }

    - (UIColor*)myColor {
        return [UIColor colorWithCGColor:  ((OCLayer *)self.layer).myColor];
    }
  @end


   @interface OCViewController : UIViewController
    @property (weak, nonatomic) IBOutlet OCView *animView;

    @end



    #import "OCViewController.h"
    #import "OCLayer.h"
    @interface OCViewController ()
    @end

    @implementation OCViewController
    -(void)viewDidAppear:(BOOL)animated{
        [super viewDidAppear:animated];
        self.animView.percent = 1;
        self.animView.myColor = [UIColor whiteColor];
        [UIView animateWithDuration:3.0
                         animations:^{
                               self.animView.percent = 20;
                               self.animView.myColor = [UIColor redColor];
                         }];



    }
    - (void)viewDidLoad {
        [super viewDidLoad];

    }
 @end
查看更多
Anthone
3楼-- · 2019-08-31 05:40

It turned out to be obvious in the end, I needed to expose a CGColor property in my CALayer and animate that instead.

Edit:

Here's some code for this, using the UIViewCustomPropertyAnimation project as a basis.

In OCLayer.h add a new property:

@property (nonatomic) CGColorRef myColor;

In OCLayer.m add the @dynamic directive:

@dynamic myColor;

And update isCustomAnimKey:

+ (BOOL)isCustomAnimKey:(NSString *)key {
    return [key isEqualToString:@"percent"] || [key isEqualToString:@"myColor"];
}

In OCView.h add the same property but as a UIColor. This already existed in my project so didn't require modification, which is great because it didn't break any code.

@property (nonatomic, strong) UIColor *progressColor;

The main changes would be in OCView.m as the getter and setter need to convert from CGColor to UIColor and back again.

- (void)setMyColor:(UIColor *)color {
    self.layer.myColor = color.CGColor;
}

- (UIColor*)myColor {
    return [UIColor colorWithCGColor: self.layer.myColor];
}

The animation can now be carried out as normal:

[UIView animateWithDuration:1.f animations:^{
    self.animView.myColor = [UIColor redColor];
}];
查看更多
登录 后发表回答