1

I'm facing similar problem as Animating a shape with CoreAnimation

I have a custom UIView

MyUIView

- (void)drawRect:(CGRect)rect {
    // Right here, I might also draw some triangle, circle which I do not wish
    // to be animated.

    // ...
    // Calculate minX, minY, ... based on rect and self->value.
    // ...

    // Draw a rounded rectangle based on minX, minY, ...
    // The x-location of rounded rectangle will be different for difference self->value
    CGContextMoveToPoint(context, minx, midy);
    CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
    ...
    ...
    CGContextDrawPath(context, kCGPathFillStroke);        
}

// Usually trigger by controller via button clicked
- (void) setValueViaButtonClicked:(double) value {
    self->currentValue = value;
    dispatch_async(dispatch_get_main_queue(), ^{
        [self setNeedsDisplay];
    });
}

Currently, whenever I click the button with a new value different from old value, I can see the rounded rectangle moved from old position to new position, but without animation.

I would like to have animation, only during explicit button click. For most of the examples I had seen, the animation code is performed within controller, by

  • Animation is done through Layer and CGPathCreateMutable
  • The creation of Layer and CGPathCreateMutable, is performed in view controller.

I was wondering, how am I possible to perform the above tasks through MyUIView's setValueViaButtonClicked? As without proper rect information from drawRect, how am I going to configure information for paths created from CGPathCreateMutable?

Community
  • 1
  • 1
Psycho Donut
  • 273
  • 1
  • 3
  • 10

1 Answers1

0

I think you don't use CoreAnimation but CoreGraphics to just draw the current state/value. Whenever it comes to animations instead of static images I switch to CoreAnimation layers. In your case I would create a UIView subclass and add a CAShapeLayer for your triangle (or whatever shape).

It would look like this:

@interface MyView ()
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property (nonatomic, assign) double currentValue;
@end

@implementation MyView

-(instancetype)init {
  if (self = [super init]) {
    [self setup];
  }
  return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
  if (self = [super initWithCoder:aDecoder]) {
    [self setup];
  }
  return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
  self = [super initWithFrame:frame];
  if (self) {
    [self setup];
  }
  return self;
}

- (void)setup {
  CAShapeLayer *shape = [CAShapeLayer layer];
  shape.contentsScale = [[UIScreen mainScreen] scale];
  self.shapeLayer = shape;
  [self.layer addSublayer:shape];
}

- (void)layoutSubviews {
  [super layoutSubviews];
  self.shapeLayer.frame = self.bounds;
  self.shapeLayer.path = [self bezierPathForValue:self.currentValue].CGPath;
}

- (UIBezierPath *)bezierPathForValue:(double)value {
  UIBezierPath *path = [UIBezierPath bezierPath];
  [path moveToPoint:...]; // points are depending on currentValue
  return path;
}

- (void)setValueViaButtonClicked:(double) value {
  double oldValue = self.currentValue;
  self.currentValue = value;

  // update model value
  self.shapeLayer.path = [self bezierPathForValue:value].CGPath;

  CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
  pathAnimation.fromValue = (id)[self bezierPathForValue:oldValue].CGPath;
  pathAnimation.toValue = (id)[self bezierPathForValue:value].CGPath;
  pathAnimation.duration = 0.3f;
  [self.shapeLayer addAnimation:pathAnimation forKey:nil];
}

@end
fruitcoder
  • 1,073
  • 8
  • 24