I want to display a view on a UIViewController
using the UIView
called LoadingView
. However, whenever I try to use the following code, only the UIView
appears without the CALayer
.
Ideally, this code would cause a UIView
to appear when startLoading()
is called. The CALayer
would then be added to the UIView
and it would be a circle that is rotating around the center of the view. What can I change to get this CALayer
to show up the right way on the UIView
?
class LoadingView: UIView {
let circleLayer = CAShapeLayer()
init() {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func startLoading() {
let size = 50
if let topView = UIApplication.topViewController()?.view {
topView.addSubview(self)
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.centerYAnchor.constraint(equalTo: topView.centerYAnchor),
self.centerXAnchor.constraint(equalTo: topView.centerXAnchor),
self.widthAnchor.constraint(equalToConstant: CGFloat(size)),
self.heightAnchor.constraint(equalToConstant: CGFloat(size)),
])
self.layer.cornerRadius = frame.height/3
addShadow(shadowColor: UIColor.label.cgColor, shadowOffset: CGSize(width: 0, height: 0), shadowOpacity: 0.3, shadowRadius: 2)
backgroundColor = .secondarySystemBackground
animateCircle(duration: .infinity)
}
}
private func animateCircle(duration: TimeInterval) {
let circlePath = UIBezierPath(arcCenter: CGPoint(x: self.frame.midX, y: self.frame.midY), radius: (frame.size.width), startAngle: 0.0, endAngle: CGFloat(Double.pi*2), clockwise: true)
// Setup the CAShapeLayer with the path, colors, and line width
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = Constants.Colors.mainBlue?.cgColor
circleLayer.lineWidth = 1.0
// Don't draw the circle initially
circleLayer.strokeEnd = 0.0
// Add the circleLayer to the view's layer's sublayers
self.layer.addSublayer(circleLayer)
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0
animation.toValue = 1
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
circleLayer.strokeEnd = 0
circleLayer.add(animation, forKey: "animateCircle")
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Double.pi*3
rotationAnimation.duration = duration
rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
circleLayer.add(rotationAnimation, forKey: nil)
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
fadeAnimation.fromValue = 1
fadeAnimation.toValue = 0
fadeAnimation.duration = duration
fadeAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
circleLayer.add(fadeAnimation, forKey: nil)
}
func stopLoading() {
self.removeFromSuperview()
}
}
2
Answers
I noticed that you don’t set the
frame
of circleLayer.Try set it as your view.
There are a few things going wrong:
The
frame
/bounds
of the CALayer need to be set accurately. Keep in mind that this could change from its initial placement. For example, in my first test, the layer believed that it was a0x0
rectangle.The path of the curve has to be correctly set (related to #1)
Your current time of
.infinity
would’ve made the animation take an infinite amount of time to complete. I think what you want instead is an animation that takes a certain amount of time and repeats infinitely.There may be been another change I’m now forgetting about, but this should get you started: