为什么我的 CaLayer 没有显示在 UIView 之上?

why isnt my CaLayer showing up on top of the UIView?

我想使用名为 LoadingViewUIViewUIViewController 上显示一个视图。但是,每当我尝试使用以下代码时,只有 UIView 出现而没有 CALayer.

理想情况下,此代码会导致在调用 startLoading() 时出现 UIView。然后 CALayer 将被添加到 UIView 并且它将是一个围绕视图中心旋转的圆。我可以更改什么以使 CALayerUIView 上以正确的方式显示?

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()
    }
    
}

我注意到您没有设置 circleLayer 的 frame。 尝试将其设置为您的视图。

有些地方出了问题:

  1. CALayer的frame/bounds需要准确设置。请记住,这可能会从其初始位置发生变化。例如,在我的第一次测试中,图层认为它是一个 0x0 矩形。

  2. 必须正确设置曲线的路径(与#1相关)

  3. 您当前 .infinity 的时间会使动画需要无限长的时间才能完成。我认为你想要的是一个需要一定时间并且 无限重复 的动画。

我现在可能忘记了另一个更改,但这应该可以帮助您入门:


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()
    }
}