macoscocoaanimationtransformnsanimation

CGAffineTransformMakeScale not working - OS X - Cocoa


I have a simple Mac app that I am developing. I want to be able to apply a transform on a NSButton and make it bigger. Twice its size. However my code is not working, it just slides the button to a corner. Does anyone know what is wrong?

[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {

    [numberButton1.animator.layer setAffineTransform:CGAffineTransformMakeScale(4.0, 4.0)];
    numberButton1.layer.anchorPoint = CGPointMake(0.5, 0.5);
    [numberButton1.animator.layer setAffineTransform:CGAffineTransformMakeScale(1.0, 1.0)];

} completionHandler:nil];

Update 1

I tried the following, but it doesn't do anything :(

   CGAffineTransform test = CGAffineTransformMakeScale(4.0, 4.0);

   [NSAnimationContext runAnimationGroup:^(NSAnimationContext *ctx) {
        numberButton1.animator.layer.affineTransform = test;
    } completionHandler:^{
        [NSAnimationContext runAnimationGroup:^(NSAnimationContext *ctx) {
            numberButton1.animator.layer.affineTransform = CGAffineTransformIdentity;
        } completionHandler:^{
            NSLog(@"Done...");
        }];
    }];

Update 2

Here is my code sudo, hopefully this will help:

My header (.h) file:

#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>

@interface ViewController : NSViewController {

    IBOutlet NSButton *numberButton1;
}

-(IBAction)demo:(id)sender;

@end

And my implementation (.m) file:

#import "ViewController.h"

@implementation ViewController

-(IBAction)demo:(id)sender {

    CGRect frame = numberButton1.layer.frame;
    CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
    numberButton1.layer.position = center;
    numberButton1.layer.anchorPoint = CGPointMake(0.5, 0.5);

    [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {

        context.allowsImplicitAnimation = YES;
        [[NSAnimationContext currentContext] setDuration:0.2];
        numberButton1.animator.layer.affineTransform = CGAffineTransformMakeScale(4.0, 4.0);

    } completionHandler:^{

        [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {

            context.allowsImplicitAnimation = YES;
            [[NSAnimationContext currentContext] setDuration:0.2];
            numberButton1.animator.layer.affineTransform = CGAffineTransformIdentity;

        } completionHandler:nil];
    }];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.

    numberButton1.wantsLayer = YES;
    numberButton1.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
    numberButton1.layer.backgroundColor = [NSColor colorWithRed:(17/255.0) green:(145/255.0) blue:(44/255.0) alpha:1.0].CGColor;
}

- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];

    // Update the view, if already loaded.
}

@end

I am also not using Auto Layout for my UI files.

Thanks, Dan.


Solution

  • The affineTransform property on CALayer is just a convenience wrapper for the transform property. On layer-backed views, you're not supposed to change this. That being said, it's possible.

    The first of your problems is that you can't animate layer properties using a NSAnimation block. You need to explicitly create a CABasicAnimation object that represents the transform animation and add it to the layer. Edit: I just remembered that it actually is possible to animate layer properties. You need to enable the allowsImplicitAnimation property on the context. This property's intended purpose is to let you animate view properties without using the animator proxy, but a side effect is that it lets you animate implicit layer properties.

    The second problem, as mentioned briefly before, is that you can't safely set the transform / anchor point on a view's layer, if your view is layer-backed. NSView will reset your layer's transform and anchor point whenever the geometry changes. You're allowed to modify these properties if the view is layer-hosting, but that comes with a whole batch of issues that you'd most likely want to avoid. There's some hacks that you can use to avoid this problem, but they require swizzling and should probably be avoided for any views which you do not own (e.g. NSButton).

    So I have to ask, why do you want to make the button twice as large? Is the zoomed state ephemeral, or is it persistent? If it's persistent, change the frame instead. Otherwise if it's transient and the geometry of the view doesn't change, you might be able to get away with modifying the two properties, as AppKit won't have a reason to reset them before you complete the animation.


    Edit: The reason why you're not seeing an animation is because your original code is applying a scale transform twice in the same transaction. When this transaction has the modifications coalesced, it'll pick the last transform applied, meaning it'll be the identity transform which, by the way, should be written as CGAffineTransformIdentity rather than a scale transform with a factor of 1. Apply the scale down animation in a new transaction by putting another animation block in the completion block of the first animation. For example:

    [NSAnimationContext runAnimationGroup:^(NSAnimationContext *ctx) {
            ctx.allowsImplicitAnimation = YES;
            layer.affineTransform = transform;
        } completionHandler:^{
            [NSAnimationContext runAnimationGroup:^(NSAnimationContext *ctx) {
                ctx.allowsImplicitAnimation = YES;
                layer.affineTransform = CGAffineTransformIdentity;
            } completionHandler:nil];
    }];
    

    Make sure the parent view of the subview that you want to scale is layer-backed before attempting the animation (wantsLayer = YES).