3

I'm trying to animate UILabel's width. For some reason animation doesn't work and label changes it's size immediately. I use autolayout. Here is the code from the sample project I wrote to reproduce this. Tap on the button changes trailing constrains for both the button and the label.

@interface ViewController ()

@property (assign, nonatomic) BOOL controlsResized;
@property (weak, nonatomic) IBOutlet UIButton *button;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *buttonTrailingConstraint;
@property (weak, nonatomic) IBOutlet MyLabel *label;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *labelTrailingConstraint;
- (IBAction)doTapButton:(id)sender;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.button setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
    self.label3.text = @"asdfasdfds sklfjfjls sdlkfj jjjjkfjkfdsjkdfsjklffsjkfdsjkl";
}    


- (IBAction)doTapButton:(id)sender
{
    if (!self.controlsResized)
    {
        [UIView animateWithDuration:0.5 animations:^{
            self.buttonTrailingConstraint.constant = 0;
            [self.button layoutIfNeeded];
            self.labelTrailingConstraint.constant = 0;
            [self.label layoutIfNeeded];

            self.controlsResized = YES;
        }];
    }
    else
    {
        [UIView animateWithDuration:0.5 animations:^{
            self.buttonTrailingConstraint.constant = 100;
            [self.button layoutIfNeeded];
            self.labelTrailingConstraint.constant = 100;
            [self.label layoutIfNeeded];

            self.controlsResized = NO;
        }];
    }
}

@implementation MyLabel

- (void)drawTextInRect:(CGRect)rect
{
    NSLog(@"drawTextInRect");
    [super drawTextInRect:rect];
}

@end

As you can see on the video, it works for the button, but doesn't work for the label.

I tried to investigate and subclassed label from MyLabel class. It appears, that - (void)drawTextInRect:(CGRect)rect gets called immediately on the button tap, as a reason of [self.label layoutIfNeeded]; in the animation block. Why is it so? I would've expect the frame to be changed animated, as it is set inside the animation block. And it works as expected for the UIButton.

Why UILabel doesn't resize animated? Is there a way to fix it?

EDIT: I tried the approaches suggested in current answers, but they don't work either. I think the animation code is not a problem here, because it works for the button in my example. The problem seems to be related to UILabel specific. UILabel seems to handle animation of size changes differently than other UIView subclasses, but I can't understand why.

Anastasia
  • 3,024
  • 1
  • 23
  • 34
  • Your code shows evidence of tampering: it sets `self.label3.text` but `ViewController` has no `label3` property. It's difficult to help you when you don't show us your real code. Anyway, I suspect there's a problem with the way your label is set up in the storyboard. – rob mayoff Jan 08 '16 at 19:57

3 Answers3

3

I would post a comment but I do not have the necessary reputation for that. Here is what I would do:

self.buttonTrailingConstraint.constant = x;
self.labelTrailingConstraint.constant = x;

[UIView animateWithDuration:0.5 animations:^{

    [self.view layoutIfNeeded];
}];

When you update a constraint you need to take into account that the respective view's constraint you are modifying is not the only one that needs updating, so a layout method call is better made on the parent view.

This worked for me everytime. Hope this helps.

Bogdan Balta
  • 121
  • 9
  • 3
    That was my initial reaction as well, but I'm getting a weird behaviour in my own tests: it works perfectly when expanding the label, but when it's reduced again it jumps... Weird. – jcaron Jan 08 '16 at 18:38
  • @Bogdan Balta: I see what you mean, you suggest the standard way to animate constraints and I know about it. However it's not a problem in my code, because button animates as expected. The problem is UILabel-specific somehow. I will change my code because it apparently confuses people. Please let me know if you can get your animation code to work with UILabel. – Anastasia Jan 08 '16 at 19:28
  • @Bogdan Balta: You seem to be right, it's really the matter of calling [self.view layoutIfNeeded]; – Anastasia Jan 08 '16 at 19:54
2

You must set constants of constraints before animate.

Try this :

- (IBAction)doTapButton:(id)sender
{
    if (!self.controlsResized)
    {
        self.buttonTrailingConstraint.constant = 0;
        self.labelTrailingConstraint.constant = 0;

        [UIView animateWithDuration:0.5 animations:^{
            [self.view layoutIfNeeded];
        } completion:^(BOOL finished) {
            self.controlsResized = YES;
        }];
    }
    else
    {
        self.buttonTrailingConstraint.constant = 100;
        self.labelTrailingConstraint.constant = 100;

        [UIView animateWithDuration:0.5 animations:^{
            [self.view layoutIfNeeded];
        } completion:^(BOOL finished) {
            self.controlsResized = NO;
        }];
    }
}
Pipiks
  • 2,018
  • 12
  • 27
  • No. I see what you mean, you suggest the standard way to animate constraints and I know about it. However it's not a problem in my code, because button animates as expected. The problem is UILabel-specific somehow. I will change my code because it apparently confuses people. Please let me know if you can get your animation code to work with UILabel. – Anastasia Jan 08 '16 at 19:27
2

There are a couple parts to making this 100% work as expected:

  • Calling layoutIfNeeded first
  • Setting the constraint constant outside of the animation block
  • Calling layoutIfNeeded inside the block

Copied from a working implementation:

[self.view layoutIfNeeded];
self.topContainerConstraint.constant = constraintValue;
[UIView animateWithDuration:0.25 animations:^{
    [self.view layoutIfNeeded];
} completion:^(BOOL finished){ }];

I'd change your function to something like the following (obviously untested):

- (IBAction)doTapButton:(id)sender
{
    if (!self.controlsResized)
    {
        [self.view layoutIfNeeded];
        self.labelTrailingConstraint.constant = 0;
        self.buttonTrailingConstraint.constant = 0;
        [UIView animateWithDuration:0.5 animations:^{
            [self.view layoutIfNeeded];
            self.controlsResized = YES;
        }];
    }
    else
    {
        [self.view layoutIfNeeded];
        self.labelTrailingConstraint.constant = 100;
        self.buttonTrailingConstraint.constant = 100;
        [UIView animateWithDuration:0.5 animations:^{
            [self.view layoutIfNeeded];
            self.controlsResized = NO;
        }];
    }
}
Ben
  • 1,117
  • 13
  • 21
  • No. I see what you mean, you suggest the standard way to animate constraints and I know about it. However it's not a problem in my code, because button animates as expected. The problem is UILabel-specific somehow. I will change my code because it apparently confuses people. Please let me know if you can get your animation code to work with UILabel. – Anastasia Jan 08 '16 at 19:26
  • Great! I'd just wired up a demo to verify too. :) – Ben Jan 08 '16 at 20:09