'Pulsing Animation on layer

I'm making some custom type of loader for an app where I just simply make 4 circular layers inside my view and it all set and display fine. but when I apply pulsing animation scaling of all layers they disturb all my design and layer scaling fine but it's change it's center point. I wants all circular layers first scaling the layer and make small it's size and make it large then again identity and it should call infinity. but should not change it's center point.

here's I'm sharing my code

class ViewController: BaseViewController{
    
    @IBOutlet weak var layerView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        drawCircles()
    }

    func drawCircles(){
        let width = layerView.bounds.width
        let halfOfView = width / 2
        
        let firstCirleLayer = UIBezierPath(arcCenter: CGPoint(x: halfOfView / 2, y: halfOfView), radius: halfOfView /  3.5, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        circleLayers(path: firstCirleLayer.cgPath, color: UIColor.purple)
        
        let secondCirleLayer = UIBezierPath(arcCenter: CGPoint(x: halfOfView, y: halfOfView / 2), radius: halfOfView /  3.5, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        circleLayers(path: secondCirleLayer.cgPath, color: UIColor.yellow)
        
        let thirdCirleLayer = UIBezierPath(arcCenter: CGPoint(x: width * 0.75, y: halfOfView), radius: halfOfView /  3.5, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        circleLayers(path: thirdCirleLayer.cgPath, color: UIColor.brown)

        let fourthCirleLayer = UIBezierPath(arcCenter: CGPoint(x: halfOfView, y: width * 0.75), radius: halfOfView /  3.5, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        circleLayers(path: fourthCirleLayer.cgPath, color: UIColor.blue)
    }
    
    func circleLayers(path: CGPath, color: UIColor){

        let layer = CAShapeLayer()
        layer.path = path
        layer.fillColor = color.cgColor
        layerView.layer.addSublayer(layer)
        
        let scaling = CABasicAnimation(keyPath: "transform.scale")
        scaling.toValue = 1.2
        scaling.duration = 0.3
        scaling.autoreverses = true
        scaling.repeatCount = .infinity
        layer.add(scaling, forKey: nil)
    }
    
}


Solution 1:[1]

To scale each circle from their center, you should set the frame of each layer to the bezier path's bounding box. Then you can just set a constant origin of CGPoint(x: radius, y: radius) for the bezier path's arcCenter.

class ViewController: UIViewController {

    @IBOutlet weak var layerView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        drawCircles()
    }

    func drawCircles(){
        let width = layerView.bounds.width
        let halfOfView = width / 2
        let radius = halfOfView / 3.5

        let firstCenter = CGPoint(x: halfOfView / 2, y: halfOfView)
        makeCircleLayer(center: firstCenter, radius: radius, color: .purple)

        let secondCenter = CGPoint(x: halfOfView, y: halfOfView / 2)
        makeCircleLayer(center: secondCenter, radius: radius, color: .yellow)

        let thirdCenter = CGPoint(x: width * 0.75, y: halfOfView)
        makeCircleLayer(center: thirdCenter, radius: radius, color: .brown)

        let fourthCenter = CGPoint(x: halfOfView, y: width * 0.75)
        makeCircleLayer(center: fourthCenter, radius: radius, color: .blue)

    }

    func makeCircleLayer(center: CGPoint, radius: CGFloat, color: UIColor) {
        
        let layer = CAShapeLayer()

        /// the frame is the actual frame/bounding box of the circle
        layer.frame = CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2)

        /// path is relative to the frame
        let path = UIBezierPath(arcCenter: CGPoint(x: radius, y: radius), radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        layer.path = path.cgPath

        layer.fillColor = color.cgColor
        layerView.layer.addSublayer(layer)

        let scaling = CABasicAnimation(keyPath: "transform.scale")
        scaling.toValue = 1.2
        scaling.duration = 0.3
        scaling.autoreverses = true
        scaling.repeatCount = .infinity
        layer.add(scaling, forKey: nil)
    }
}

Result:

Each circle scales from its center

However, because your circles are just circles (not a complicated shape), you should just use UIView's with a corner radius. The following code produces the same result, but is much cleaner.

class ViewController: UIViewController {

    @IBOutlet weak var layerView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        drawCircles()
    }

    func drawCircles() {
        let width = layerView.bounds.width
        let halfOfView = width / 2
        let radius = halfOfView / 3.5

        let firstCenter = CGPoint(x: halfOfView / 2, y: halfOfView)
        makeCircleView(center: firstCenter, radius: radius, color: .purple)

        let secondCenter = CGPoint(x: halfOfView, y: halfOfView / 2)
        makeCircleView(center: secondCenter, radius: radius, color: .yellow)

        let thirdCenter = CGPoint(x: width * 0.75, y: halfOfView)
        makeCircleView(center: thirdCenter, radius: radius, color: .brown)

        let fourthCenter = CGPoint(x: halfOfView, y: width * 0.75)
        makeCircleView(center: fourthCenter, radius: radius, color: .blue)
    }

    func makeCircleView(center: CGPoint, radius: CGFloat, color: UIColor) {
        let frame = CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2)
        let circleView = UIView(frame: frame)
        circleView.backgroundColor = color
        circleView.layer.cornerRadius = radius
        layerView.addSubview(circleView)
        
        UIView.animate(withDuration: 0.3, delay: 0, options: [.repeat, .autoreverse]) {
            circleView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1