почему кассир иногда не появляется?

#ios #swift #iphone #xcode #uiview

Вопрос:

Я использую следующий пользовательский класс UIView в качестве индикатора загрузки в своем приложении. Большую часть времени это отлично работает, и я могу просто использовать loadingView.startLoading() его, чтобы он появился. Однако в редких случаях появляется только UIView с тенью за ним, а CAShapeLayer нигде не видно. Что может помешать кассовому слою появиться поверх UIView LoadingView?

Вид загрузки

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

Комментарии:

1. Я удивлен, что это когда-либо срабатывает. Вы не можете добавить подслой и анимировать его все за один раз.

2. @мэтт, ты прав, я внес изменения, чтобы они не были сделаны одновременно, и это исправило ситуацию. Почему подслой и анимация не могут произойти одновременно?

Ответ №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
    }
}
 

Ответ №2:

рекомендуется вызывать этот метод в основном потоке с помощью DispatchQueue, так как иногда элемент управления может находиться в фоновом потоке, поэтому он должен быть возвращен в основной поток перед выполнением любых обновлений пользовательского интерфейса