3
votes

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.

3
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

3 Answers

3
votes

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.

2
votes

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;
        }];
    }
}
2
votes

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;
        }];
    }
}