skip to Main Content

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


  1. I noticed that you don’t set the frame of circleLayer.
    Try set it as your view.

    Login or Signup to reply.
  2. There are a few things going wrong:

    1. 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 a 0x0 rectangle.

    2. The path of the curve has to be correctly set (related to #1)

    3. 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:

    
    class LoadingView: UIView {
        
        let circleLayer = CAShapeLayer()
        
        private var circlePath : UIBezierPath = .init()
        let size : CGFloat = 50
        
        init() {
            super.init(frame: .zero)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func startLoading() {
            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
                backgroundColor = .secondarySystemBackground
                self.layer.addSublayer(circleLayer)
                calculateCirclePath()
                animateCircle(duration: 1, repeats: true)
            }
        }
        
        func calculateCirclePath() {
            self.circlePath = UIBezierPath(arcCenter: CGPoint(x: size / 2, y: size / 2), radius: size / 2, startAngle: 0.0, endAngle: CGFloat(Double.pi*2), clockwise: true)
        }
        
        override func layoutSubviews() {
            circleLayer.frame = self.layer.bounds
        }
        
        private func animateCircle(duration: TimeInterval, repeats: Bool) {
            // Setup the CAShapeLayer with the path, colors, and line width
            circleLayer.path = circlePath.cgPath
            circleLayer.fillColor = UIColor.clear.cgColor
            circleLayer.strokeColor = UIColor.blue.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.repeatCount = repeats ? .infinity : 1
            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.repeatCount = repeats ? .infinity : 1
            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.repeatCount = repeats ? .infinity : 1
            fadeAnimation.fromValue = 1
            fadeAnimation.toValue = 0
            fadeAnimation.duration = duration
            fadeAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
            circleLayer.add(fadeAnimation, forKey: nil)
        }
        
        func stopLoading() {
            self.removeFromSuperview()
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search