Animate intrinsicContentSize changes
Asked Answered
C

6

59

I have a UIView subclass that draws a circle whose radius changes (with nice bouncy animations). The view is deciding the size of the circle.

I want this UIView subclass to change its frame size to match the animated changes to the circle radius, and I want these changes to modify any NSLayoutConstraints connected to the view (so that views that are constrained to the edge of the circle will move as the circle resizes).

I understand that implementing -(CGSize)intrinsicContentSize and calling invalidateIntrinsicContentSize when the radius changes will tell constraints to update, but I cant figure out how to animate the changes to intrinsicContentSize.

Calling invalidateIntrinsicContentSize from within a [UIView animateWith... block just instantly updates the layout.

Is this even possible, and is there a workaround/better approach?

Coacervate answered 17/6, 2013 at 18:11 Comment(1)
I have a feeling that this is not possible. Take the example of the UILabel its intrinsicContentSize is a function of its font size. However there is no nice way to animate a font change. The workaround for the label is to animate its layer's transform change (using a CAAnimation) then to change the font after the animation is complete - Not nice.Tammy
L
80

invalidateIntrinsicContentSize works well with animations and layoutIfNeeded. The only thing you need to consider is, that changing the intrinsic content size invalidates the layout of the superview. So this should work:

[UIView animateWithDuration:0.2 animations:^{
    [self invalidateIntrinsicContentSize];
    [self.superview setNeedsLayout];
    [self.superview layoutIfNeeded];
}];
Limpid answered 16/7, 2014 at 15:47 Comment(2)
Ok, works if you add the line [self.superview setNeedsLayout] in the middleAshmead
Updated with @Ash's feedback.Limpid
B
11

Swift version of @stigi's answer which worked for me:

UIView.animate(withDuration: 0.2, animations: {
    self.invalidateIntrinsicContentSize()
    self.superview?.setNeedsLayout()
    self.superview?.layoutIfNeeded()
})
Become answered 4/8, 2017 at 16:37 Comment(0)
S
3

Width / height constraint doesn't help? Keep reference of this constraint and ...

[NSLayoutConstraint constraintWithItem:view
                             attribute:NSLayoutAttributeWidth
                             relatedBy:NSLayoutRelationEqual
                                toItem:nil
                             attribute:NSLayoutAttributeNotAnAttribute
                            multiplier:1
                              constant:myViewInitialWidth];

... when you do want to animate myView resize, do this ...

self.viewWidthConstraint.constant = 100; // new width
[UIView animateWithDuration:0.3 animations:^{ [view layoutIfNeeded]; }];

... do the same thing for the height.

Depends on your other constraints, maybe you will be forced to raise priority of these two constraints.

Or you can subclass UIView, add - (void)invalidateIntrinsicContentSize:(BOOL)animated and fake it by yourself. Get new size from - (CGSize)intrinsicContentSize and animate it by animating width / height constraints. Or add property to enable / disable animations and override invalidateIntrinsicContentSize and do it inside this method. Many ways ...

Samothrace answered 10/9, 2013 at 23:53 Comment(0)
K
3

In the code below, intrinsic class is the class that has just changed it's size on changing a variable. To animate the intrinsic class only use the code below. If it impacts other objects higher up the view hierarchy then replace self.intrinsic class with the top level view for setNeedsLayout and layoutIfNeeded.

[UIView animateWithDuration:.2 animations:^{
        self.intrinsicClass.numberOfWeeks=8;
        [self.intrinsicClass setNeedsLayout];
        [self.intrinsicClass layoutIfNeeded];
    }];
Kurt answered 23/6, 2014 at 13:4 Comment(1)
This is especially true for UIViews with intrinsicContentSize inside UIStackView. In order for the stack view to animate on resize, you must call layoutIfNeeded on the parent of the stack, not on the stack itself. I just got bitten by this!Flanders
K
0

None of this has worked for me. I have a UILabel which I am setting with a NSAttributedString. The text is multiline and wrapping on word boundaries. Therefore the height is variable. I've tried this:

[UIView animateWithDuration:1.0f animations:^{
    self.label = newLabelText;
    [self.label invalidateIntrinsicContentSize];
    [self.view setNeedsLayout];
    [self.view layoutIfNeeded];
}];

And a number of variations. None work. The label immediately changes it's size and then slides into it's new position. So the animation of the labels position ons screen is working. But the animating of the label's size change is not.

Kero answered 5/5, 2015 at 0:52 Comment(1)
This could be a comment, because it does not supply an answer.Sacculate
D
0

Well for Swift 4/3 this works and I think this is best practise. If you have a UIView with a UILabel in it and the UIView adapts the frame from the UILabel, use this:

self.theUILabel.text = "text update"
UIView.animate(withDuration: 5.0, animations: {
   self.theUIView.layoutIfNeeded()
})

The normal self.view.layoutIfNeeded() will work most of the time as well.

Drury answered 18/8, 2017 at 0:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.