iOS 13 прерывает переход UIViewPropertyAnimator

У нас есть сломанный переход в WeatherKit, воспроизводимый только в бета-версии iOS 13. Мы не уверены, является ли это ошибкой UIKit или мы делаем что-то ужасно неправильное.

С массивом UIViewPropertyAnimator, работавшим до iOS 13, начиная с iOS 13 (во всех бета-версиях) кадр анимации не обновляется правильно. Например, у меня есть UIViewPropertyAnimator с именем labelAnimator, который анимирует метку для определенного CGRect, этот CGRect не соблюдается, а метка анимируется где-то еще, как показано на видео.

Любопытно, что если я возился с порядком переходов в массиве, нижний лист работает нормально, и единственное, что анимируется неправильно, — это метка температуры.

Вот код, который анимирует весь этот вид:

class MainView: UIViewController {
    var panGesture = UIPanGestureRecognizer()
    var tapGesture = UITapGestureRecognizer()
    let animationDuration: TimeInterval = 0.75
    var diff: CGFloat = 150

    @IBOutlet weak var gradientView: GradientView!
    @IBOutlet weak var detailedViewContainer: UIView!
    @IBOutlet weak var blurView: UIVisualEffectView!

    override func viewDidLoad() {
        self.panGesture.addTarget(self, action: #selector(MainView.handlePanGesture(gesture:)))
        self.detailedViewContainer.addGestureRecognizer(self.panGesture)
        self.tapGesture.addTarget(self, action: #selector(MainView.handleTapGesture(gesture:)))
        self.detailedViewContainer.addGestureRecognizer(self.tapGesture)
    }

    enum PanelState {
        case expanded
        case collapsed
    }

    var nextState: PanelState {
        return panelIsVisible ? .collapsed : .expanded
    }
    var panelIsVisible: Bool = false
    var runningAnimations = [UIViewPropertyAnimator]()
    var animationProgressWhenInterrupted: CGFloat = 0.0

     @objc func handleTapGesture(gesture: UITapGestureRecognizer)  {
        switch gesture.state {
        case .ended:
            tapAnimation()
        default: break
        }
    }

    @objc func tapAnimation(){
        self.panGesture.isEnabled = false
        self.tapGesture.isEnabled = false
        startInteractiveTransition(state: nextState, duration: animationDuration)
        updateInteractiveTransition(fractionComplete: 0)
        let linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.8, y: -0.16), controlPoint2: CGPoint(x: 0.22, y: 1.18))
        continueInteractiveTransition(timingParameters: linearTiming){
            self.panGesture.isEnabled = true
            self.tapGesture.isEnabled = true
        }
    }

     @objc func handlePanGesture(gesture: UIPanGestureRecognizer)  {
        switch gesture.state {
        case .began:
            if !panelIsVisible ? gesture.velocity(in: nil).y < 0 : gesture.velocity(in: nil).y > 0 {
                startInteractiveTransition(state: nextState, duration: animationDuration)
            }
        case .changed:
            let translation = gesture.translation(in: self.detailedViewContainer)
            var fractionComplete = (translation.y / view.bounds.height * 2)
            fractionComplete = !panelIsVisible ? -fractionComplete : fractionComplete
            updateInteractiveTransition(fractionComplete: fractionComplete)
        case .ended:
            let linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.8, y: -0.16), controlPoint2: CGPoint(x: 0.22, y: 1.18))

            continueInteractiveTransition(timingParameters: linearTiming) {
                self.panGesture.isEnabled = true
                self.tapGesture.isEnabled = true
            }
            NotificationCenter.default.post(name: .resetHeaders, object: nil)
            NotificationCenter.default.post(name: .disableScrolling, object: nil, userInfo: ["isDisabled": nextState == .collapsed])

        default:
            break
        }
    }

        // MARK: - Animations
    func animateTransitionIfNeeded(state: PanelState, duration: TimeInterval) {
        if runningAnimations.isEmpty {
            // MARK: Frame
            var linearTiming = UICubicTimingParameters(animationCurve: .easeOut)
            linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.1, y: 0.1), controlPoint2: CGPoint(x: 0.1, y: 0.1))
            let frameAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
            frameAnimator.addAnimations {
                switch state {
                case .expanded:
                    self.detailedViewContainer.frame = CGRect(x: 0, y: self.diff, width: self.view.bounds.width, height: self.view.bounds.height - self.diff)
                case .collapsed:
                    self.detailedViewContainer.frame = CGRect(x: 0, y: self.view.bounds.height - self.view.safeAreaInsets.bottom - 165, width: self.view.bounds.width, height: 200)
                }
            }

            // MARK: Arrow
            let arrowAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
            arrowAnimator.addAnimations {
                switch state {
                case .expanded:
                    self.leftArrowPath.transform = CGAffineTransform(rotationAngle: 15 * CGFloat.pi / 180)
                    self.rightArrowPath.transform = CGAffineTransform(rotationAngle: 15 * -CGFloat.pi / 180)
                case .collapsed:
                    self.leftArrowPath.transform = CGAffineTransform(rotationAngle: 15 * -CGFloat.pi / 180)
                    self.rightArrowPath.transform = CGAffineTransform(rotationAngle: 15 * CGFloat.pi / 180)
                }
                self.leftArrowPath.center.y = self.detailedViewContainer.frame.origin.y + 15
                self.rightArrowPath.center.y = self.detailedViewContainer.frame.origin.y + 15
            }

            // MARK: Scale
            let radiusAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
            radiusAnimator.addAnimations{
                switch state {
                case .expanded:
                    self.gradientView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
                    self.gradientView.layer.maskedCorners = [.layerMaxXMinYCorner,.layerMinXMinYCorner]
                    self.gradientView.layer.cornerRadius = dataS.hasTopNotch ? 20 : 14
                case .collapsed:
                    self.gradientView.transform = CGAffineTransform.identity
                    self.gradientView.layer.maskedCorners = [.layerMaxXMinYCorner,.layerMinXMinYCorner]
                    self.gradientView.layer.cornerRadius = 0
                }
            }

            // MARK: Blur
            let blurTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.5, y: 0.25), controlPoint2: CGPoint(x: 0.5, y: 0.75))
            let blurAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: blurTiming)
            blurAnimator.addAnimations {
                switch state {
                case .expanded:
                    self.blurView.effect = UIBlurEffect(style: .dark)
                case .collapsed:
                    self.blurView.effect = nil
                }
            }

            // MARK: Text
            let textAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
            textAnimator.addAnimations({
                switch state{
                case .expanded:
                    self.tempLabel.transform =  CGAffineTransform(scaleX: 0.6, y: 0.6)
                    self.tempLabel.frame =  CGRect(origin: CGPoint(x: 15, y: self.diff / 2 - self.tempLabel.frame.height / 2), size: self.tempLabel.frame.size)
                    self.descriptionLabel.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
                    self.descriptionLabel.alpha = 0
                    self.descriptionLabel.transform = CGAffineTransform(translationX: 0, y: -100)
                    self.summaryLabel.frame = CGRect(origin: CGPoint(x: self.blurView.contentView.center.x, y: 10), size: self.summaryLabel.frame.size)
                case .collapsed:
                    self.descriptionLabel.transform = CGAffineTransform.identity
                    self.descriptionLabel.alpha = 1
                    self.tempLabel.transform = CGAffineTransform.identity
                    self.tempLabel.frame = CGRect(origin: CGPoint(x: 15, y: self.view.frame.height / 2 - self.tempLabel.frame.height / 2 - 30), size: self.tempLabel.frame.size) 
                    self.summaryLabel.frame = CGRect(origin: CGPoint(x: self.blurView.contentView.center.x, y: self.tempLabel.center.y - self.summaryLabel.frame.height / 2), size: self.summaryLabel.frame.size)
                }
            }, delayFactor: 0.0)

            let summaryLabelTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.05, y: 0.95), controlPoint2: CGPoint(x: 0.15, y: 0.95))
            let summaryLabelTimingReverse = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.95, y: 0.5), controlPoint2: CGPoint(x: 0.85, y: 0.05))

            // MARK: Summary Label
            let summaryLabelAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: state == .collapsed ? summaryLabelTiming : summaryLabelTimingReverse)
            summaryLabelAnimator.addAnimations {
                switch state{
                case .expanded:
                    self.summaryLabel.alpha = 1
                case .collapsed:
                    self.summaryLabel.alpha = 0
                }
            }


            radiusAnimator.startAnimation()
            runningAnimations.append(radiusAnimator)

            blurAnimator.scrubsLinearly = false
            blurAnimator.startAnimation()
            runningAnimations.append(blurAnimator)


            summaryLabelAnimator.scrubsLinearly = false
            summaryLabelAnimator.startAnimation()
            runningAnimations.append(summaryLabelAnimator)

            frameAnimator.startAnimation()
            runningAnimations.append(frameAnimator)

            textAnimator.startAnimation()
            textAnimator.pauseAnimation()

            runningAnimations.append(textAnimator)

            arrowAnimator.startAnimation()
            runningAnimations.append(arrowAnimator)



            // Clear animations when completed
            runningAnimations.last?.addCompletion { _ in
                self.runningAnimations.removeAll()
                self.panelIsVisible = !self.panelIsVisible
                textAnimator.startAnimation()

            }
        }
    }

    /// Called on pan .began
    func startInteractiveTransition(state: PanelState, duration: TimeInterval) {
        if runningAnimations.isEmpty {
            animateTransitionIfNeeded(state: state, duration: duration)
            for animator in runningAnimations {
                animator.pauseAnimation()
                animationProgressWhenInterrupted = animator.fractionComplete
            }
        }

        let hapticSelection = SelectionFeedbackGenerator()
        hapticSelection.prepare()
        hapticSelection.selectionChanged()
    }

    /// Called on pan .changed
    func updateInteractiveTransition(fractionComplete: CGFloat) {
        for animator in runningAnimations {
            animator.fractionComplete = fractionComplete + animationProgressWhenInterrupted
        }
    }

    /// Called on pan .ended
    func continueInteractiveTransition(timingParameters: UICubicTimingParameters? = nil, durationFactor: CGFloat = 0, completion: @escaping ()->()) {
        for animator in runningAnimations {
            animator.continueAnimation(withTimingParameters: timingParameters, durationFactor: durationFactor)
        }
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + animationDuration) {
            completion()
        }

    }
}

И вот видео о проблеме в iOS 13 и о том, как она сейчас работает в iOS 12. .


person perte    schedule 01.08.2019    source источник
comment
Любая помощь будет невероятно полезна!   -  person perte    schedule 01.08.2019
comment
вместо того, чтобы ссылаться на суть, лучше всего включить код здесь в блок кода, чтобы, если ссылка сломается, вопрос все еще оставался в силе. Также облегчает чтение людям   -  person Scriptable    schedule 01.08.2019
comment
Отредактировал пост.   -  person perte    schedule 01.08.2019
comment
У меня тоже такое бывает на бета-версии iOS 13   -  person onmyway133    schedule 06.08.2019
comment
@onmyway133 onmyway133 мы начали переписывать это представление в SwiftUI... из-за отсутствия решения.   -  person perte    schedule 06.08.2019


Ответы (1)


У меня такая же проблема, для меня параметр UIViewPropertyAnimator continueAnimation durationFactor является проблемой, когда он не равен 0, после нескольких анимаций табличное представление сходит с ума.

person Markicevic    schedule 18.10.2019