Kobe Kobe - 3 months ago 11
Swift Question

Create a circular indicator using a custom class has width and height too large

I want to make an circular indicator using

CAShapeLayer
. I am doing this in a custom class so I can reuse it for every
UIView
.

This is what I have for now:

class CircularIndicator: UIView {

var circularIndicator = CAShapeLayer()

override init(frame: CGRect) {
super.init(frame: frame)

configure()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)

configure()
}

func configure() {
let centerPoint = CGPoint(x: self.bounds.width / 2, y: self.bounds.width / 2)
let circleRadius = self.bounds.size.width / 2
let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true)

circularIndicator.path = circlePath.cgPath
circularIndicator.strokeColor = UIColor.blue.cgColor
circularIndicator.fillColor = UIColor.clear.cgColor
circularIndicator.lineWidth = 6
circularIndicator.strokeStart = 0
circularIndicator.strokeEnd = 1

self.layer.addSublayer(circularIndicator)
}
}


I added an
UIView
in the main view controller with height and width = 150. My issue is that when I run the app, the
circleRadius
value ends up being 500, which makes the circle way bigger.

My understand is that
self.bounds.witdh
or
self.bounds.height
should equal the value of the
UIView
which contains this class.
Am I wrong on this ? How come I end up with value so large for width and height ?

Rob Rob
Answer

The issue is that the frame may change. So you should set the path in layoutSubviews:

class CircularIndicator: UIView {

    var circularIndicator = CAShapeLayer()
    var lineWidth: CGFloat = 6

    override init(frame: CGRect) {
        super.init(frame: frame)

        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        configure()
    }

    private func configure() {
        circularIndicator.strokeColor = UIColor.blue.cgColor
        circularIndicator.fillColor = UIColor.clear.cgColor
        circularIndicator.lineWidth = lineWidth
        circularIndicator.strokeStart = 0
        circularIndicator.strokeEnd = 1

        layer.addSublayer(circularIndicator)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let centerPoint = CGPoint(x: bounds.width / 2, y: bounds.width / 2)
        let circleRadius = (bounds.size.width - lineWidth) / 2

        circularIndicator.path = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true).cgPath
    }
}

By the way, you may want to inset the circle by half of the lineWidth, as shown above, where I adjust the circleRadius accordingly. Otherwise, the edges of the circle will exceed the frame of the view.