I am trying to animate a rotation of a layer's CGpath.
The rotation works perfectly, but for some reason, the path seems to shrink, then get bigger again. I just want it to show a rotating animation. I don't want it to shrink
here is a video.
here is the code
class ViewController: UIViewController {
@IBOutlet weak var overlayView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let maskLayer = CAShapeLayer()
let path = UIBezierPath(rect: view.bounds)
let mask = UIBezierPath.drawSquare(width: 100, center: view.center)
// Makes it so we can see through the center of the view
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
path.append(mask)
maskLayer.path = path.cgPath
overlayView.layer.mask = maskLayer
}
@IBAction func rotateView(_ sender: Any) {
let maskPath = UIBezierPath.drawSquare(width: 100, center: overlayView.center)
let path = UIBezierPath(rect: overlayView.frame)
let bounds: CGRect = maskPath.cgPath.boundingBox
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radians = 90 / 180.0 * .pi
var transform: CGAffineTransform = .identity
transform = transform.translatedBy(x: center.x, y: center.y)
transform = transform.rotated(by: radians)
transform = transform.translatedBy(x: -center.x, y: -center.y)
maskPath.apply(transform)
path.append(maskPath)
// create new animation
let anim = CABasicAnimation(keyPath: "path")
anim.toValue = path.cgPath
(overlayView.layer.mask as? CAShapeLayer)?.add(anim, forKey: "path")
}
}
extension UIBezierPath{
static func drawSquare(width: CGFloat, center: CGPoint) -> UIBezierPath {
let rect = CGRect(x: center.x - width/2, y: center.y - width/2, width: width, height: width)
let path = UIBezierPath()
path.move(to: rect.origin)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.close()
return path
}
}
Your "cutout box" is shrinking and growing because you're using path animation.
When you animate a path from one set of points to another, each point will take a straight line from its starting point to its ending point.
Couple ways to visualize this...
First, we'll add an "outline" frame to the cutout mask box:
as you see, each corner is taking a direct route to its next position.
If we highlight one side, it's maybe a bit more obvious:
and, we can number the corners for even more clarity:
So, if we want to rotate the square without changing its size, we could use keyframe animation and run the corners along a circle:
or, much easier, rotate the mask layer instead of its path:
let radians = 90 / 180.0 * .pi
let anim = CABasicAnimation(keyPath: "transform.rotation.z")
anim.toValue = radians
anim.duration = 1.0
(overlayView.layer.mask as? CAShapeLayer)?.add(anim, forKey: "layerRotate")
No doubt we notice that the mask layer frame does not completely cover the overlayView frame.
So, we can fix that by making the mask layer frame larger.
We could calculate exactly how big it needs to be, but because shape layers are very efficient, we'll just make it a square, 1.5 times bigger than the longer axis:
so when it rotates, it covers the view:
and we finish with this: