UIView animateWithDuration doesn't animate cornerRadius variation

26,379

Solution 1

tl;dr: Corner radius is not animatable in animateWithDuration:animations:.


What the documentation says about view animations.

As the section on Animations in the "View Programming Guide for iOS" says

Both UIKit and Core Animation provide support for animations, but the level of support provided by each technology varies. In UIKit, animations are performed using UIView objects

The full list of properties that you can animate using either the older

[UIView beginAnimations:context:];
[UIView setAnimationDuration:];
// Change properties here...
[UIView commitAnimations];

or the newer

[UIView animateWithDuration:animations:];

(that you are using) are:

  • frame
  • bounds
  • center
  • transform (CGAffineTransform, not the CATransform3D)
  • alpha
  • backgroundColor
  • contentStretch

As you can see, cornerRadius is not in the list.

Some confusion

UIView animations is really only meant for animating view properties. What confuses people is that you can also animate the same properties on the layer inside the UIView animation block, i.e. the frame, bounds, position, opacity, backgroundColor. So people see layer animations inside animateWithDuration and believe that they can animate any view property in there.

The same section goes on to say:

In places where you want to perform more sophisticated animations, or animations not supported by the UIView class, you can use Core Animation and the view’s underlying layer to create the animation. Because view and layer objects are intricately linked together, changes to a view’s layer affect the view itself.

A few lines down you can read the list of Core Animation animatable properties where you see this one:

  • The layer’s border (including whether the layer’s corners are rounded)

So to animate the cornerRadius you need to use Core Animation as you've already said in your updated question (and answer). I just added tried to explain why its so.


Some extra clarification

When people read the documentations that says that animateWithDuration is the recommended way of animating it is easy to believe that it is trying to replace CABasicAnimation, CAAnimationGroup, CAKeyframeAnimation, etc. but its really not. Its replacing the beginAnimations:context: and commitAnimations that you seen above.

Solution 2

I use this extension to animate change of corner radius:

extension UIView
{
    func animateCornerRadius(from: CGFloat, to: CGFloat, duration: CFTimeInterval)
    {
        CATransaction.begin()
        let animation = CABasicAnimation(keyPath: "cornerRadius")
        animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
        animation.fromValue = from
        animation.toValue = to
        animation.duration = duration
        CATransaction.setCompletionBlock { [weak self] in
            self?.layer.cornerRadius = to
        }
        layer.add(animation, forKey: "cornerRadius")
        CATransaction.commit()
    }
}

Solution 3

A corner radius variation can be animated but the only way to do so is to use a CABasicAnimation. Hope this helps somebody out there.

Solution 4

Starting in iOS 10 you can actually animate cornerRadius:

UIViewPropertyAnimator(duration: 3.0, curve: .easeIn) {
    square.layer.cornerRadius = 20
}.startAnimation()

Solution 5

You can implement UIView animation as here: http://maniacdev.com/2013/02/ios-uiview-category-allowing-you-to-set-up-customizable-animation-properties

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
animation.duration = DURATION;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.toValue = @(NEW_CORNER_RADIUS);
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
[view.layer addAnimation:animation forKey:@"setCornerRadius:"];
Share:
26,379

Related videos on Youtube

Francesco Puglisi
Author by

Francesco Puglisi

Updated on July 05, 2022

Comments

  • Francesco Puglisi
    Francesco Puglisi almost 2 years

    I'm trying to animate the change of the cornerRadius of a UIView instance layer, but the variation of the cornerRadius takes place immediately.

    Here's the code:

    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 100, 100)];
    view.layer.masksToBounds = YES;
    view.layer.cornerRadius = 10.0;
    [UIView animateWithDuration:1.0 animations:^{
        
        [view.layer.cornerRadius = 0.0;
        
    }];
    

    Thanks everybody who is going to give me any tips.

    EDIT:

    I managed to animate this property using Core Animation, using a CABasicAnimation.

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    animation.fromValue = [NSNumber numberWithFloat:10.0f];
    animation.toValue = [NSNumber numberWithFloat:0.0f];
    animation.duration = 1.0;
    [viewToAnimate.layer addAnimation:animation forKey:@"cornerRadius"];
    [animation.layer setCornerRadius:0.0];
    
    • Mohammad Abdurraafay
      Mohammad Abdurraafay over 12 years
      Try this, to make your animation to stick to new values: animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards;
    • David Rönnqvist
      David Rönnqvist almost 11 years
      @MohammadAbdurraafay No, please don't. That is the wrong way of making an animation "stick".
    • Leon Storey
      Leon Storey over 10 years
      @DavidRönnqvist Why shouldn't animation.removedOnCompletion = NO; be used? You give no alternative suggestions either here or in your answer.
    • Leon Storey
      Leon Storey over 10 years
      @DavidRönnqvist The link didn't attach.
    • David Rönnqvist
      David Rönnqvist over 10 years
      @user1763532 Let's try that again: my answer here explains some problems with removeOnCompletion = NO and gives one alternate solution.
    • rob mayoff
      rob mayoff about 8 years
    • Francesco Puglisi
      Francesco Puglisi about 8 years
      @robmayoff The other one may be a duplicate as it was asked only two months ago, whereas this was asked 4 years ago.
    • rob mayoff
      rob mayoff about 8 years
      It doesn't matter which one is older. It matters which one has a working answer. (If age mattered, stackoverflow wouldn't offer the option to close this one as a duplicate of that one.)
  • Francesco Puglisi
    Francesco Puglisi about 13 years
    Well... if you use the old approach for animations, it will work... But if you try to use the approach suggested by Apple, than it doesn't animate.
  • David Rönnqvist
    David Rönnqvist almost 12 years
    @singingAtom animateWithDuration: and CABasicAnimations is actually not the same thing so there is no new or old approach. animateWithDuration is not a part of Core Animation and can only animate view properties, (cornerRadius is a layer property). The real old approach that animateWithDuration replaces is [UIView beginAnimations:context:] and [UIView commitAnimation] which work exactly the same way as the new approach. Apple suggests using animateWithDuration: for view properties and continues on to say that you should use Core Animation for more advanced animations.
  • SpacyRicochet
    SpacyRicochet over 10 years
    Good answer. Props for pointing out all the documentation as well.
  • Wyetro
    Wyetro over 7 years
    Worked really well. You don't really need the from field since it should really always be layer.cornerRadius.
  • xaphod
    xaphod about 7 years
    Thank you, worked well inside my UIView.animate block, ie combining the movement of a UIView with a change in its cornerRadius
  • Jassi
    Jassi almost 7 years
    Perfect answer. Helped a lot. Thank you so much.
  • akaDuality
    akaDuality over 6 years
    Since iOS 11 cornerRadius is animatable property
  • edwardmp
    edwardmp almost 6 years
    This changed in iOS 11, please refer to my answer
  • infiniteLoop
    infiniteLoop almost 6 years
    Its available from iOS 10+.
  • Asela Liyanage
    Asela Liyanage over 4 years