swiftuiviewcashapelayer

My CAShapeLayer is not well centered in my View


I wan't to draw a circle in the grey View but it is not perfectly centered and I can't understand why.

I get the center of my view (circleView) and put it in the circle's center then i had CAShapeLayers to the superview

let shapeLayer = CAShapeLayer()
let shapeLayer2 = CAShapeLayer()

let center = circleView.center
let height = circleView.frame.height

// First circle

let circularPath = UIBezierPath(arcCenter: center, radius: height/2-20 , startAngle: -CGFloat.pi / 2, endAngle: (2 * CGFloat.pi) - (CGFloat.pi / 2), clockwise: true)
shapeLayer.path = circularPath.cgPath
shapeLayer.lineWidth = 20
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor(named: "color1")!.cgColor
shapeLayer.strokeEnd = 1
        
view.layer.addSublayer(shapeLayer)
        

// Second circle

let circularPath2 = UIBezierPath(arcCenter: center, radius: height/2-20, startAngle: -CGFloat.pi / 2, endAngle: (2 * CGFloat.pi) - (CGFloat.pi / 2), clockwise: true)
shapeLayer2.path = circularPath2.cgPath
shapeLayer2.lineWidth = 20
shapeLayer2.fillColor = UIColor.clear.cgColor
shapeLayer2.strokeColor = UIColor(named: "color")!.cgColor
shapeLayer2.strokeEnd = 0.5
        
view.layer.addSublayer(shapeLayer2)

But here is what i get

enter image description here

I tried to get center of my view like this :

CGPoint(x: circleView.bounds.midX, y: circleView.bounds.midY)

and tried to add the shapeLayer to my circleView rather than the superview but it's not working neither. Do you have any idea ?


Solution

  • The best way to manage layers is to subclass UIView and set the sizes, path points, etc in layoutSubviews().

    Here's a quick example based on your code (I don't know what your "color" or "color1" are, so I set them to green and systemGreen):

    class CircleView: UIView {
        
        private let shapeLayer = CAShapeLayer()
        private let shapeLayer2 = CAShapeLayer()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            
            // set shape layer properties and add them as sublayers
            
            shapeLayer.lineWidth = 20
            shapeLayer.fillColor = UIColor.clear.cgColor
            //shapeLayer.strokeColor = UIColor(named: "color1")!.cgColor
            shapeLayer.strokeColor = UIColor.systemGreen.cgColor
            shapeLayer.strokeEnd = 1
            
            self.layer.addSublayer(shapeLayer)
    
            shapeLayer2.lineWidth = 20
            shapeLayer2.fillColor = UIColor.clear.cgColor
            //shapeLayer2.strokeColor = UIColor(named: "color")!.cgColor
            shapeLayer2.strokeColor = UIColor.green.cgColor
            shapeLayer2.strokeEnd = 0.5
            
            self.layer.addSublayer(shapeLayer2)
    
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
            
            let center = CGPoint(x: bounds.midX, y: bounds.midY)
            let height = bounds.height
            
            // we can use the same path for both layers
            let circularPath = UIBezierPath(arcCenter: center,
                                            radius: height * 0.5 - 20.0,
                                            startAngle: -.pi * 0.5, // start at minus 90-degrees (12 o'clock)
                                            endAngle: .pi * 1.5,    // end at 270-degrees (also 12 o'clock)
                                            clockwise: true)
    
            shapeLayer.path = circularPath.cgPath
            shapeLayer2.path = circularPath.cgPath
    
        }
    }
    

    Now we add the view to a controller:

    class ViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let circleView = CircleView()
            circleView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(circleView)
            NSLayoutConstraint.activate([
                circleView.widthAnchor.constraint(equalToConstant: 300.0),
                circleView.heightAnchor.constraint(equalTo: circleView.widthAnchor),
                circleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                circleView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            ])
    
            circleView.backgroundColor = .lightGray
    
        }
    
    }
    

    and the result is:

    enter image description here