UPDATE: if the deployment target is iOS 11 or later:
Starting with iOS 11, UIKit will animate cornerRadius if you update it inside the animation block. Just set your layer.cornerRadius view in the UIView animation UIView or (to handle interface orientation changes), set it to layoutSubviews or viewDidLayoutSubviews .
ORIGINAL: If the deployment target is older than iOS 11:
So you want this:

(I have included Debug> Slow Animations to make viewing smoothness easier.)
Problem, do not skip this paragraph: it turns out to be much more complicated than it should be, because the iOS SDK does not make parameters (duration, time curve) autorotation animation is available in a convenient way. You can (I think) get on them by overriding -viewWillTransitionToSize:withTransitionCoordinator: on your view controller to call -animateAlongsideTransition:completion: in the transition coordinator, and in the callback you get transitionDuration and completionCurve from the UIViewControllerTransitionCoordinatorContext . And then you need to transfer this information to your CircleView , which should save it (since it has not yet been changed!), And later, when it receives layoutSubviews , it can use it to create a CABasicAnimation for cornerRadius with saved animation parameters. And do not accidentally create an animation when the size is not animated ... The end of a side statement.
Wow, that sounds like a ton of work, and you need to bring in a view controller. Here is another approach fully implemented inside CircleView . It works now (in iOS 9), but I can’t guarantee that it will always work in the future, because it makes two assumptions that could theoretically be wrong in the future.
Here's the approach: override -actionForLayer:forKey: in CircleView to return an action that sets up animation for cornerRadius at startup.
These are two assumptions:
bounds.origin and bounds.size get separate animations. (This is true now, but presumably future iOS could use single animation for bounds . It would be enough to just check the bounds animation if the bounds.size animation bounds.size not found.)- The
bounds.size animation bounds.size added per layer before Core Animation requests a cornerRadius action.
Given these assumptions, when Core Animation requests a cornerRadius action, we can get the bounds.size animation from the layer, copy it, and change the copy for the cornerRadius animation. The copy has the same animation parameters as the original (if we do not modify them), so it has the correct duration and time curve.
Here's the start of CircleView :
class CircleView: UIView { override func layoutSubviews() { super.layoutSubviews() updateCornerRadius() } private func updateCornerRadius() { layer.cornerRadius = min(bounds.width, bounds.height) / 2 }
Note that the boundaries of the view are set before the view receives layoutSubviews , and therefore, before updating cornerRadius . This is why the bounds.size animation bounds.size set before the cornerRadius animation cornerRadius . Each property animation is set inside the property setting tool.
When we install cornerRadius , Core Animation asks us to launch CAAction :
override func action(for layer: CALayer, forKey event: String) -> CAAction? { if event == "cornerRadius" { if let boundsAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation { let animation = boundsAnimation.copy() as! CABasicAnimation animation.keyPath = "cornerRadius" let action = Action() action.pendingAnimation = animation action.priorCornerRadius = layer.cornerRadius return action } } return super.action(for: layer, forKey: event) }
In the above code, if we are asked to perform an action for cornerRadius , we will search for CABasicAnimation on bounds.size . If we find it, we copy it, change the key path to cornerRadius and save it in a user-defined CAAction ( Action class, which I will show below). We also save the current value of the cornerRadius property, because Core Animation calls actionForLayer:forKey: to , updating the property.
After returning actionForLayer:forKey: Core Animation updates the cornerRadius property of this layer. Then it launches the action by sending it runActionForKey:object:arguments: The task of the action is to install any animation. Here's the custom subclass of CAAction , which I CircleView inside of CircleView :
private class Action: NSObject, CAAction { var pendingAnimation: CABasicAnimation? var priorCornerRadius: CGFloat = 0 public func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { if let layer = anObject as? CALayer, let pendingAnimation = pendingAnimation { if pendingAnimation.isAdditive { pendingAnimation.fromValue = priorCornerRadius - layer.cornerRadius pendingAnimation.toValue = 0 } else { pendingAnimation.fromValue = priorCornerRadius pendingAnimation.toValue = layer.cornerRadius } layer.add(pendingAnimation, forKey: "cornerRadius") } } } }
The runActionForKey:object:arguments: method sets animation properties fromValue and toValue , and then adds animation to the layer. There is a complication: UIKit uses “additive” animations because they work better if you run another animation in the property while the previous animation is still working. Therefore, our action verifies this.
If the animation is additive, it sets fromValue to the difference between the old and new corner radii and sets toValue to zero. Since the layer cornerRadius property cornerRadius already been updated by the time the animation starts, adding that fromValue at the beginning of the animation makes it look like the old corner radius and adding a toValue zero at the end of the animation makes it look like the new corner radius.
If the animation is not additive (which does not happen if UIKit created the animation, as far as I know), then it simply sets fromValue and toValue obvious way.
Here is the whole file for your convenience:
import UIKit class CircleView: UIView { override func layoutSubviews() { super.layoutSubviews() updateCornerRadius() } private func updateCornerRadius() { layer.cornerRadius = min(bounds.width, bounds.height) / 2 } override func action(for layer: CALayer, forKey event: String) -> CAAction? { if event == "cornerRadius" { if let boundsAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation { let animation = boundsAnimation.copy() as! CABasicAnimation animation.keyPath = "cornerRadius" let action = Action() action.pendingAnimation = animation action.priorCornerRadius = layer.cornerRadius return action } } return super.action(for: layer, forKey: event) } private class Action: NSObject, CAAction { var pendingAnimation: CABasicAnimation? var priorCornerRadius: CGFloat = 0 public func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { if let layer = anObject as? CALayer, let pendingAnimation = pendingAnimation { if pendingAnimation.isAdditive { pendingAnimation.fromValue = priorCornerRadius - layer.cornerRadius pendingAnimation.toValue = 0 } else { pendingAnimation.fromValue = priorCornerRadius pendingAnimation.toValue = layer.cornerRadius } layer.add(pendingAnimation, forKey: "cornerRadius") } } } }
My answer was inspired by this answer of Simon .