1

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.

Supertecnoboff
  • 6,406
  • 11
  • 57
  • 98

1 Answers1

6

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).

sudo rm -rf
  • 29,408
  • 19
  • 102
  • 161
  • Hi, thanks so much for your answer. So basically I am making a game and when a user presses the NSButton, I want it to enlarge and then get smaller again. This is just a nice fun simple little animation which I want in the app. – Supertecnoboff Apr 04 '15 at 16:04
  • I set the allowsImplicitAnimation to YES and while the animation is smooth now, it still doesn't enlarge the button. – Supertecnoboff Apr 04 '15 at 16:05
  • Hey again. I have been playing around with this but I still havn't managed to get this to work. See my updated question for the my code. Do you by any chance have any over ideas? – Supertecnoboff May 02 '15 at 18:13
  • 1
    Enable `allowsImplicitAnimation` in your context. – sudo rm -rf May 02 '15 at 20:22
  • Ok that helped a lot! I'm almost there. The one last problem I have is that when the animation happens, it seems to only enlarge the NSButton text only. I want it to enlarge the entire NSButton. I have done this animation before with UIButton on iOS so I know it works. I just want the whole button to become bigger, so its view needs to enlarge (not just its text label). How can I do this? – Supertecnoboff May 02 '15 at 20:56
  • Are you sure you're transforming the right view? An instance of `NSButton` is just a view. A simple test of scaling a button's layer as I showed above worked fine for me. Without seeing your code I can't say why that would happen. – sudo rm -rf May 02 '15 at 21:34
  • Ok sudo, I have updated my question with my full code. Thanks very mush for your help once again. – Supertecnoboff May 03 '15 at 11:24
  • From what you've given me I don't see a reason why it shouldn't work. See this example: http://cl.ly/2s3j0h160333 – sudo rm -rf May 03 '15 at 21:39
  • Thank you so much!!!! It works now :) The key was this line of code: ```self.view.wantsLayer = YES;``` – Supertecnoboff May 04 '15 at 08:55
  • Plus one for explainig allowsImplicitAnimation – Jeff Jun 08 '20 at 06:57