Search code examples
ioscalayeruiviewanimation

CALayer Animation Scale with fixed centre on iOS7


I've been banging my head over what I thought would have been an easy thing. A pulsing circle, there are several example on SO, but I'm still unable to fix one last thing.

With the code below, I do have a circle that expand and contracts fine, but during contraction, the scaling is done from the CGRect's layer frame (upper left corner). Expansion works fine, and both expansion/contraction work fine on iOS8. The problem only happens on iOS7.

I tried with setting the anchor point, translating the view while expanding ... nothing seemed to do the trick... I'm doing something wrong, I don't know what.

If anyone want to try with the code below you just need a UIViewController, a label, and a button, wired to the IBOutlets below.

That's my last - known - bug before I can submit my app to the app Store... :\

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController


@end




#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (weak, nonatomic) IBOutlet UIButton *button;
@property (nonatomic) BOOL isExpanded;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.isExpanded = YES;
    self.label.layer.cornerRadius = self.label.frame.size.width/2;
    self.label.layer.masksToBounds = YES;

    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];

}
- (IBAction)ExpandOrCollapse:(id)sender {
    [self.button setEnabled:NO];
    CGFloat animationDuration = 0.30f; // Your duration
    CGFloat animationDelay = 0.0f; // Your delay (if any)

    if (self.isExpanded) {

       CGAffineTransform t = CGAffineTransformMakeScale(.17f, .17f);

        CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        scaleAnimation.duration = animationDuration;
        scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0f];

        scaleAnimation.toValue = [NSNumber numberWithFloat:0.17f];

        [self.label.layer addAnimation:scaleAnimation forKey:@"scale"];

        [UIView animateWithDuration:animationDuration
                              delay:animationDelay
                            options: UIViewAnimationOptionCurveEaseIn
                         animations:^{
                         }
                         completion:^(BOOL finished){
                             self.isExpanded = NO;
                             [self.label setTransform:t];
                             [self.button setEnabled:YES];
                         }];
    }else{

        CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        scaleAnimation.duration = animationDuration;

        scaleAnimation.fromValue = [NSNumber numberWithFloat:0.17f];

        scaleAnimation.toValue = [NSNumber numberWithFloat:1.0f];

        [self.label.layer addAnimation:scaleAnimation forKey:@"scale"];

        [UIView animateWithDuration:0.3
                              delay:0
                            options: UIViewAnimationOptionCurveEaseIn
                         animations:^{

                         }
                         completion:^(BOOL finished){
                             [self.button setEnabled:YES];
                             [self.label setTransform:CGAffineTransformMakeScale(1.0, 1.0f)];
                             self.isExpanded = YES;
                         }];
    }
}
@end

EDIT

for eiran, here's the code with his modifications. Problem is still present unfortunately..

    #import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (weak, nonatomic) IBOutlet UIButton *button;
@property (nonatomic) BOOL isExpanded;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.isExpanded = YES;
    self.label.layer.cornerRadius = self.label.frame.size.width/2;
    self.label.layer.masksToBounds = YES;

    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];

}
- (IBAction)ExpandOrCollapse:(id)sender {
    [self.button setEnabled:NO];
    CGFloat animationDuration = 0.30f; // Your duration
    CGFloat animationDelay = 0.0f; // Your delay (if any)

    if (self.isExpanded) {
        [UIView animateWithDuration:animationDuration
                              delay:animationDelay
                            options: UIViewAnimationOptionCurveEaseIn
                         animations:^{
                             [self.label setTransform:CGAffineTransformScale(CGAffineTransformIdentity, 0.17f, 0.17f)];
                         }
                         completion:^(BOOL finished){
                             self.isExpanded = NO;
                             [self.button setEnabled:YES];
                         }];
    }else{

        [UIView animateWithDuration:0.3
                              delay:0
                            options: UIViewAnimationOptionCurveEaseIn
                         animations:^{
                             [self.label setTransform:CGAffineTransformScale(CGAffineTransformIdentity, 1.0f, 1.0f)];
                         }
                         completion:^(BOOL finished){
                             [self.button setEnabled:YES];

                             self.isExpanded = YES;
                         }];
    }
}

Solution

  • This seemed to work for me:

    [UIView animateWithDuration:0.75
                          delay:0
                        options:UIViewAnimationOptionRepeat|UIViewAnimationOptionAutoreverse|UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.95, 0.95);
                     }completion:^(BOOL b){
                     }];
    

    that's a repeating scale animation.

    EDIT: OK then:) apple works in mysterious ways. i've found a solution for you, but i'm not sure what the problem is. in ios 7.1, if you work with a storyboard, the scaling does what you say. BUT, if you add a component dynamically, the animation works fine. add this to your code and check it out:

          - (void)viewDidLoad {
        [super viewDidLoad];
        self.isExpanded = YES;
    
    
        UIButton* b = [UIButton buttonWithType:UIButtonTypeCustom];
        [b setFrame:CGRectMake(100, 400, 120, 40)];
        [b addTarget:self action:@selector(expand:) forControlEvents:UIControlEventTouchUpInside];
        [b setBackgroundColor:[UIColor blueColor]];
        [b setTitle:@"added dynamically" forState:UIControlStateNormal];
        [self.view addSubview:b];
    
        }
    
        -(void)expand:(UIButton*)sender{
            CGFloat animationDuration = 0.30f; // Your duration
            CGFloat animationDelay = 0.0f; // Your delay (if any)
    
        if (self.isExpanded) {
            [UIView animateWithDuration:animationDuration
                                  delay:animationDelay
                                options: UIViewAnimationOptionCurveEaseIn
                             animations:^{
                                 [sender setTransform:CGAffineTransformScale(CGAffineTransformIdentity, 0.8f, 0.8f)];
                             }
                             completion:^(BOOL finished){
                                 self.isExpanded = NO;
                             }];
        }else{
    
            [UIView animateWithDuration:0.3
                                  delay:0
                                options: UIViewAnimationOptionCurveEaseIn
    
    
                        animations:^{
                             [sender setTransform:CGAffineTransformScale(CGAffineTransformIdentity, 1.0f, 1.0f)];
                         }
                         completion:^(BOOL finished){
                             self.isExpanded = YES;
                         }];
    }
    
    }
    

    as you said, it was fixed on ios 8. i thought it might have something to do with constraints, but it looks like it doesn't.

    added a project that demonstrates this test here: the test

    hope that helps, eiran.