为什么我的 CaLayer 没有出现在 UIView 之上?

问题描述

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

理想情况下,此代码会在调用 UIView 时导致出现 startLoading()。然后 CALayer 将被添加UIView 中,它将是一个围绕视图中心旋转的圆。我可以更改什么才能使此 CALayer 以正确的方式显示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()
    }
    
}

解决方法

有些地方出了问题:

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

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