4

我们WeatherKit只能在 iOS 13 测试版上重现中断的过渡。我们不确定这是一个UIKit错误还是我们做错了什么。

在 iOS 13 之前的一系列UIViewPropertyAnimator工作中,自从 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 中的工作方式。

4

1 回答 1

0

我有同样的问题,对我来说 UIViewPropertyAnimator continueAnimation durationFactor 参数是问题,只要它不是0,在几个动画之后表格视图就会变得疯狂。

于 2019-10-18T08:46:12.450 回答