为什么 CAShapeLayer 有时不显示?

why isn't the CAShapeLayer showing up sometimes?

提问人:helloworld12345 提问时间:6/20/2021 最后编辑:helloworld12345 更新时间:6/21/2021 访问量:84

问:

我使用以下自定义 UIView 类作为应用程序中的加载指示器。大多数时候它效果很好,我可以用它来让它出现。但是,在极少数情况下,只有 UIView 出现,其后面有阴影,而 CAShapeLayer 无处可见。是什么原因阻止了 CAShapeLayer 显示在 LoadingView UIView 的顶部?loadingView.startLoading()

加载视图

class LoadingView: UIView {
    
//MARK: - Properties and Init
    
    let circleLayer = CAShapeLayer()
    private var circlePath : UIBezierPath = .init()
    let size : CGFloat = 80
    
    init() {
        super.init(frame: CGRect(x: 0, y: 0, width: size, height: size))
        tag = 100
        addShadow(shadowColor: UIColor.label.cgColor, shadowOffset: CGSize(width: 0, height: 0), shadowOpacity: 0.3, shadowRadius: 3)
        backgroundColor = .secondarySystemBackground
        self.layer.addSublayer(circleLayer)
        calculateCirclePath()
        animateCircle(duration: 1, repeats: true)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
//MARK: - Private UI Setup Methods
    
    private func calculateCirclePath() {
        self.circlePath = UIBezierPath(arcCenter: CGPoint(x: size / 2, y: size / 2), radius: size / 3, 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 = 5.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)
    }
}

ViewController 扩展

extension ViewController {
    func startLoading() {
            view.addSubview(loadingView)
            loadingView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                loadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                loadingView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                loadingView.widthAnchor.constraint(equalToConstant: CGFloat(80)),
                loadingView.heightAnchor.constraint(equalToConstant: CGFloat(80)),
            ])
        loadingView.isHidden = false
    }
    
    func stopLoading() {
        loadingView.isHidden = true
    }
}
iOS Swift iPhone Xcode UIViview

评论

0赞 matt 6/20/2021
我很惊讶这曾经有效。您无法一次性添加子图层并为其添加动画效果。
0赞 helloworld12345 6/21/2021
@matt你是对的,我做了一个改变,所以他们没有同时完成,它修复了它。为什么子图层和动画不能一气呵成?

答:

0赞 helloworld12345 6/21/2021 #1

因为子图层和动画不能一次性完成,所以我将代码更改为下面显示的内容,现在它运行良好!

加载视图

class LoadingView: UIView {
    
//MARK: - Properties and Init
    
    let circleLayer = CAShapeLayer()
    private var circlePath : UIBezierPath = .init()
    let size : CGFloat = 80
    
    init() {
        super.init(frame: CGRect(x: 0, y: 0, width: size, height: size))
        tag = 100
        addShadow(shadowColor: UIColor.label.cgColor, shadowOffset: CGSize(width: 0, height: 0), shadowOpacity: 0.3, shadowRadius: 3)
        backgroundColor = .secondarySystemBackground
        layer.cornerRadius = size/8
        self.layer.addSublayer(circleLayer)
        calculateCirclePath()
        circleLayer.path = circlePath.cgPath
        circleLayer.fillColor = UIColor.clear.cgColor
        circleLayer.strokeColor = UIColor.blue.cgColor
        circleLayer.lineWidth = 5.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)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
//MARK: - Private UI Setup Methods
    
    private func calculateCirclePath() {
        self.circlePath = UIBezierPath(arcCenter: CGPoint(x: size / 2, y: size / 2), radius: size / 3, startAngle: 0.0, endAngle: CGFloat(Double.pi*2), clockwise: true)
    }
    
    override func layoutSubviews() {
        circleLayer.frame = self.layer.bounds
    }
    
    func animateCircle(duration: TimeInterval, repeats: Bool) {
        // Setup the CAShapeLayer with the path, colors, and line width
        // 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)
    }
}

ViewController 扩展

extension ViewController {
    func startLoading() {
            view.addSubview(loadingView)
            loadingView.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                loadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                loadingView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                loadingView.widthAnchor.constraint(equalToConstant: CGFloat(80)),
                loadingView.heightAnchor.constraint(equalToConstant: CGFloat(80)),
            ])
        loadingView.isHidden = false
        loadingView.animateCircle(duration: 1, repeats: true)
    }
    
    func stopLoading() {
        loadingView.isHidden = true
    }
}
0赞 HAMZA ZULFQAR 6/21/2021 #2

更好的做法是使用 dispatchQueue 在主线程中调用此方法,因为有时控件可能在后台线程中,因此在执行任何 UI 更新之前必须将其返回到主线程