I am adding a custom view to another view who then has its constraints set programatically. This results in the content of the view being drawn in the wrong place, and I'm not sure why. Here's the code I have and what I tried so far:
// Called in viewDidLoad
let buttonView = UIView()
buttonView.translatesAutoresizingMaskIntoConstraints = false
let ring = CircularProgressView()
ring.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
ring.backgroundColor = .yellow
buttonView.addSubview(ring)
self.view.addSubview(buttonView)
NSLayoutConstraint.activate([
buttonView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
buttonView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -28),
buttonView.heightAnchor.constraint(equalToConstant: 80),
buttonView.widthAnchor.constraint(equalToConstant: 80),
ring.centerXAnchor.constraint(equalTo: buttonView.centerXAnchor),
ring.centerYAnchor.constraint(equalTo: buttonView.centerYAnchor)
])
I tried calling ring.layoutIfNeeded() or setting the width and height of the ring view with constraints, and it didn't change anything.
ring.heightAnchor.constraint(equalToConstant: 80),
ring.widthAnchor.constraint(equalToConstant: 80)
The CircularProgressView class looks like this. I tried using bounds instead of frame here or not halving the values by 2 and again no change.
class CircularProgressView: UIView {
// First create two layer properties
private var circleLayer = CAShapeLayer()
private var progressLayer = CAShapeLayer()
private var currentValue: Double = 0
override init(frame: CGRect) {
super.init(frame: frame)
createCircularPath()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createCircularPath()
}
func createCircularPath() {
let circularPath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0),
radius: 41,
startAngle: -.pi / 2,
endAngle: 3 * .pi / 2,
clockwise: true)
circleLayer.path = circularPath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.lineCap = .round
circleLayer.lineWidth = 3.0
circleLayer.strokeColor = UIColor.black.withAlphaComponent(0.2).cgColor
progressLayer.path = circularPath.cgPath
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineCap = .round
progressLayer.lineWidth = 3.0
progressLayer.strokeEnd = 0
progressLayer.strokeColor = UIColor.black.cgColor
layer.addSublayer(circleLayer)
layer.addSublayer(progressLayer)
let circularProgressAnimation = CABasicAnimation(keyPath: "strokeEnd")
circularProgressAnimation.duration = 2
circularProgressAnimation.toValue = 1
circularProgressAnimation.fillMode = .forwards
circularProgressAnimation.isRemovedOnCompletion = false
progressLayer.add(circularProgressAnimation, forKey: "progressAnim")
progressLayer.pauseAnimation()
}
}
This all works with no issue if done via the storyboard, but I need to create it programatically for this use case, and I'm a little confused about what's wrong.
You need to make both views programmatically like
// Called in viewDidLoad
let buttonView = UIView()
buttonView.translatesAutoresizingMaskIntoConstraints = false
let ring = CircularProgressView()
ring.translatesAutoresizingMaskIntoConstraints = false
ring.backgroundColor = .yellow
buttonView.addSubview(ring)
self.view.addSubview(buttonView)
NSLayoutConstraint.activate([
buttonView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -12),
buttonView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -28),
buttonView.heightAnchor.constraint(equalToConstant: 80),
buttonView.widthAnchor.constraint(equalToConstant: 80),
ring.centerXAnchor.constraint(equalTo: buttonView.centerXAnchor),
ring.centerYAnchor.constraint(equalTo: buttonView.centerYAnchor),
ring.heightAnchor.constraint(equalToConstant: 80),
ring.widthAnchor.constraint(equalToConstant: 80)
])
and Call createCircularPath
only inside layoutSubviews
override func layoutSubviews() {
super.layoutSubviews()
createCircularPath()
}