Search code examples
ioscore-animationcalayeranchorpoint

Positioning sublayer on center of animated UIView


I have a UIView shaped like a circle that expands and contracts. I need to have a sublayer positioned at its center that stays the same size as it is animating. The code below contains four sample views showing things I have tried. It can be copied and pasted as is.

  1. The first UIView is a not animated. Its sublayer is positioned at its center, but it has to be positioned relative to the UIView's origin. This is a problem when UIView's bounds and origin is changing during an animation.
  2. The second UIView has two animations: one for @"bounds" and one for @"cornerRadius". This arrangement causes the sublayer to end up at the screen's origin (top left).
  3. The third UIView also has two animations: this time for @"bounds.size" and @"cornerRadius". The sublayer stays the same size, which is good, but it does not stay at the center of the UIView. I tried to use [thirdView.layer setNeedsDisplayOnBoundsChange:YES]; to compensate for the bounds change but it didn't work. I also tried thirdView.contentMode = UIViewContentModeRedraw;.
  4. The fourth UIView has one animation for @"transform.scale", which seems to be a bit more efficient as far as expansion animations go. The sublayer stays at the center of the UIView like I want it, but it does not stay the same size. I need to have the size of the sublayer be independent because I intend to animate it.

Here's all the code. It looks like a lot but it's some pretty simple stuff.

    - (void)viewDidLoad {
    [super viewDidLoad];
    float screenWidth = [UIScreen mainScreen].bounds.size.width;
    float screenHeight = [UIScreen mainScreen].bounds.size.height;

    UIView * firstView = [[UIView alloc] initWithFrame:CGRectMake((screenWidth/4) - 25, (screenHeight/4) - 25, 50, 50)];
    // [firstView.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
    firstView.backgroundColor = [UIColor blueColor];
    firstView.layer.cornerRadius = 25;
    NSLog(@"firstView anchor point %@", [NSValue valueWithCGPoint:firstView.layer.anchorPoint]);

    CALayer * firstLayer = [CALayer layer];
    firstLayer.backgroundColor = [UIColor redColor].CGColor;
    [firstView.layer addSublayer:firstLayer];
    firstLayer.bounds = CGRectMake(0,0, 30, 30);
    firstLayer.cornerRadius = 15;
    firstLayer.position = CGPointMake(firstView.bounds.size.width/2,        firstView.bounds.size.height/2);
    [self.view addSubview:firstView];


    UIView * secondView = [[UIView alloc] initWithFrame:CGRectMake((screenWidth*3/4) - 25, (screenHeight/4) - 25, 50, 50)];
    secondView.backgroundColor = [UIColor blueColor];
    secondView.layer.cornerRadius = 25;

    CALayer * secondLayer = [CALayer layer];
    secondLayer.backgroundColor = [UIColor redColor].CGColor;
    [secondView.layer addSublayer:secondLayer];
    secondLayer.bounds = CGRectMake(0, 0, 30, 30);
    secondLayer.cornerRadius = 15;
    secondLayer.position = CGPointMake(secondView.bounds.size.width/2, secondView.bounds.size.height/2);

    CABasicAnimation * secondViewBoundsAnimation;
    secondViewBoundsAnimation = [CABasicAnimation animation];
    secondViewBoundsAnimation.keyPath = @"bounds";
    secondViewBoundsAnimation.fromValue = [NSValue valueWithCGRect:CGRectMake((screenWidth*3/4) - 25, (screenHeight/4) - 25, 50, 50)];
    secondViewBoundsAnimation.toValue = [NSValue valueWithCGRect:CGRectMake((screenWidth*3/4) - 50, (screenHeight/4) - 50, 100, 100)];

    CABasicAnimation * secondViewCornerRadiusAnimation;
    secondViewCornerRadiusAnimation = [CABasicAnimation animation];
    secondViewCornerRadiusAnimation.keyPath = @"cornerRadius";
    secondViewCornerRadiusAnimation.fromValue = @25;
    secondViewCornerRadiusAnimation.toValue = @50;

    CAAnimationGroup * secondViewAnimationGroup;
    secondViewAnimationGroup = [CAAnimationGroup animation];
    secondViewAnimationGroup.animations = [NSArray arrayWithObjects:secondViewBoundsAnimation, secondViewCornerRadiusAnimation, nil];
    secondViewAnimationGroup.duration = 1.5;
    secondViewAnimationGroup.repeatCount = HUGE_VALF;
    secondViewAnimationGroup.autoreverses = YES;

    [secondView.layer addAnimation:secondViewAnimationGroup forKey:@"secondViewAnimation"];

    [self.view addSubview:secondView];


    UIView * thirdView;
    // [thirdView.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
    thirdView = [[UIView alloc] initWithFrame:CGRectMake((screenWidth/4) - 25, (screenHeight*3/4) -25, 50, 50)];
    thirdView.backgroundColor = [UIColor blueColor];
    thirdView.layer.cornerRadius = 25;
    NSLog(@"thirdView anchor point %@", [NSValue valueWithCGPoint:thirdView.layer.anchorPoint]);

    CALayer * thirdLayer = [CALayer layer];
    thirdLayer.backgroundColor = [UIColor redColor].CGColor;
    [thirdView.layer addSublayer:thirdLayer];
    thirdLayer.bounds = CGRectMake(0, 0, 30, 30);
    thirdLayer.cornerRadius = 15;
    thirdLayer.position = CGPointMake(thirdView.bounds.size.width/2, thirdView.bounds.size.height/2);

    CABasicAnimation * thirdViewSizeAnimation;
    thirdViewSizeAnimation = [CABasicAnimation animation];
    thirdViewSizeAnimation.keyPath = @"bounds.size";
    thirdViewSizeAnimation.fromValue = [NSValue    valueWithCGSize:CGSizeMake(50, 50)];
    thirdViewSizeAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(100, 100)];

    CABasicAnimation * thirdViewCornerRadiusAnimation;
    thirdViewCornerRadiusAnimation = [CABasicAnimation animation];
    thirdViewCornerRadiusAnimation.keyPath = @"cornerRadius";
    thirdViewCornerRadiusAnimation.fromValue = @25;
    thirdViewCornerRadiusAnimation.toValue = @50;

    CAAnimationGroup * thirdViewAnimationGroup;
    thirdViewAnimationGroup = [CAAnimationGroup animation];
    thirdViewAnimationGroup.animations = [NSArray arrayWithObjects:thirdViewSizeAnimation, thirdViewCornerRadiusAnimation, nil];
    thirdViewAnimationGroup.duration = 1.5;
    thirdViewAnimationGroup.repeatCount = HUGE_VALF;
    thirdViewAnimationGroup.autoreverses = YES;

    //    thirdView.contentMode = UIViewContentModeRedraw;
    //    [thirdView.layer setNeedsDisplayOnBoundsChange:YES];
    [thirdView.layer addAnimation:thirdViewAnimationGroup forKey:@"thirdViewAnimation"];

    [self.view addSubview:thirdView];



    UIView * fourthView = [[UIView alloc] initWithFrame:CGRectMake((screenWidth*3/4) - 25, (screenHeight*3/4) - 25, 50, 50)];
    fourthView.backgroundColor = [UIColor blueColor];
    fourthView.layer.cornerRadius = 25;


    CALayer * fourthLayer = [CALayer layer];
    fourthLayer.backgroundColor = [UIColor redColor].CGColor;
    [fourthView.layer addSublayer:fourthLayer];
    fourthLayer.bounds = CGRectMake(0, 0, 30, 30);
    fourthLayer.cornerRadius = 15;
    fourthLayer.position = CGPointMake(fourthView.bounds.size.width/2, fourthView.bounds.size.height/2);

    CABasicAnimation * fourthViewScaleAnimation;
    fourthViewScaleAnimation = [CABasicAnimation animation];
    fourthViewScaleAnimation.keyPath = @"transform.scale";
    fourthViewScaleAnimation.fromValue = @1;
    fourthViewScaleAnimation.toValue = @2;
    fourthViewScaleAnimation.duration = 1.5;
    fourthViewScaleAnimation.repeatCount = HUGE_VALF;
    fourthViewScaleAnimation.autoreverses = YES;

    [fourthView.layer addAnimation:fourthViewScaleAnimation forKey:@"fourthViewAnimation"];

    [self.view addSubview:fourthView];

Since changing the origin of the UIView isn't possible, I need to find out a way to do either one of these things:

  1. Consistently update the sublayer's position to compensate for a changing origin.
  2. Make it so that the sublayer's size on the fourth example does not change as the UIView is scaling.

I can't do multiple UIViews because I intend to add the entire layer to an MKAnnotationView.

Thanks!


Solution

  • I figured out a solution and I feel stupid that I didn't think of it immediately. I can use two layers. The UIView does't change size. One layer expands and contracts, and another layer on top of it stays in the same position since the UIView isn't moving.