UIViewPropertyAnimator问题与自动布局 [英] UIViewPropertyAnimator issue with Autolayout

查看:96
本文介绍了UIViewPropertyAnimator问题与自动布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我尝试根据Apple WWDC重复执行的代码,但具有自动布局功能:

Here is the code of what I tried to repeat according to Apple WWDC but with autolayout:

    extension AugmentedReallityViewController {
    @objc func handlePan(recognizer: UIPanGestureRecognizer) {
//        // hide languages and units anyway
//        moveUnitView(show: false)
//        moveLanguageView(show: false)
//
//        let isNowExpanded = settingsPanelState == SettingsPanelState.expanded
//        let newState = isNowExpanded ? SettingsPanelState.collapsed : SettingsPanelState.expanded
//
//        switch recognizer.state {
//        case .began:
//            startInteractiveTransition(state: newState, duration: 1)
//            isLastPanelUpdateToReachTheNewState = true // just in case, but we should change this property later
//        case .changed:
//            let translation = recognizer.translation(in: viewSettings)
//            let fractionComplete = translation.y / viewSettings.frame.size.height
//
//            // we will use this property when interaction ends
//            if fractionComplete != 0 { // if it's == 0 , we need to use prev data
//                isLastPanelUpdateToReachTheNewState = (newState == SettingsPanelState.expanded && fractionComplete < 0) || (newState == SettingsPanelState.collapsed && fractionComplete > 0)
//            }
//
//            updateInteractiveTransition(fractionComplete: fractionComplete)
//        case .ended:
//            continueInteractiveTransition(cancel: !isLastPanelUpdateToReachTheNewState)
//        default:
//            break
//        }
    }

    @objc func handleSettingsTap() {
        // hide languages and units anyway
        moveUnitView(show: false)
        moveLanguageView(show: false)

        let isNowExpanded = settingsPanelState == SettingsPanelState.expanded
        let newState = isNowExpanded ? SettingsPanelState.collapsed : SettingsPanelState.expanded

        animateOrReverseRunningTransition(state: newState, duration: 10)
    }

    // perform all animations with animators if not already running
    private func animateTransitionIfNeeded(state: SettingsPanelState, duration: TimeInterval) {

        if runningAnimators.isEmpty {

//            // define constraint for frame animation
//            // update constraints
//            switch state {
//            case .expanded:
//                constraint_settingsView_bottom.constant = 0
//            case .collapsed:
//                constraint_settingsView_bottom.constant = -constraint_height_settingViewWhitePart.constant
//            }
            // animate that
            let frameAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear, animations: { [weak self] in
                if let strongSelf = self {
                    // define constraint for frame animation
                    // update constraints
                    switch state {
                    case .expanded:
                        strongSelf.constraint_settingsView_bottom.constant = 0
                    case .collapsed:
                        strongSelf.constraint_settingsView_bottom.constant = -(strongSelf.constraint_height_settingViewWhitePart.constant)
                    }
                }

                self?.view.layoutIfNeeded()
            })
            frameAnimator.startAnimation()
            runningAnimators.append(frameAnimator)
            frameAnimator.addCompletion({ [weak self] (position) in
                if position == UIViewAnimatingPosition.end { // need to remove this animator from array
                    if let index = self?.runningAnimators.index(of: frameAnimator) {
                        print("removed animator because of completion")
                        self?.runningAnimators.remove(at: index)
                        // we can change state to a new one
                        self?.settingsPanelState = state
                    }
                    else {
                        print("animator completion with state = \(position)")
                    }
                }
            })
        }
    }

    // starts transition if neccessary or reverses it on tap
    private func animateOrReverseRunningTransition(state: SettingsPanelState, duration: TimeInterval) {
        if runningAnimators.isEmpty { // start transition from start to end
            animateTransitionIfNeeded(state: state, duration: duration)
        }
        else { // reverse all animators
            for animator in runningAnimators {
                animator.stopAnimation(true)
                animator.isReversed = !animator.isReversed
                // test
                print("tried to reverse")
            }
        }
    }

    // called only on pan .begin
    // starts transition if neccessary and pauses (on pan .begin)
    private func startInteractiveTransition(state: SettingsPanelState, duration: TimeInterval) {
        animateTransitionIfNeeded(state: state, duration: duration)
        for animator in runningAnimators {
            animator.pauseAnimation()

            // save progress of any item
            progressWhenInterrupted = animator.fractionComplete
        }
    }

    // scrubs transition on pan .changed
    private func updateInteractiveTransition(fractionComplete: CGFloat) {
        for animator in runningAnimators {
            animator.fractionComplete = fractionComplete + progressWhenInterrupted
        }
    }

    // continue or reverse transition on pan .ended
    private func continueInteractiveTransition(cancel: Bool) {
        for animator in runningAnimators {
            // need to continue or reverse
            if !cancel {
                let timing = UICubicTimingParameters(animationCurve: .easeOut)
                animator.continueAnimation(withTimingParameters: timing, durationFactor: 0)
            }
            else {
                animator.isReversed = true
            }
        }
    }

    private func addPanGustureRecognizerToSettings() {
        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(AugmentedReallityViewController.handlePan(recognizer:)))
//        panGestureRecognizer.cancelsTouchesInView = false
        viewSettings.addGestureRecognizer(panGestureRecognizer)
    }

    private func addTapGestureRecognizerToSettings() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(AugmentedReallityViewController.handleSettingsTap))
        tapGestureRecognizer.cancelsTouchesInView = false
        viewSettingsTopTriangle.addGestureRecognizer(tapGestureRecognizer)
    }
}

现在我正在测试点击手势.并且有两个主要问题:

Right now I'm just testing tap gestures. And there are 2 main issues:

1)拍子识别器在动画过程中无法正常工作.但是在苹果WWDC中,它们更改了帧(不是像我这样的约束),并且拍子识别器工作得很好

1) Tap recognizer doesn't work properly during animation. But in apple WWDC they changed frames (not constraints like in my case) and tap recognizers worked perfectly

2)如果我更改反向属性,则更改约束确实非常糟糕.我还有多余的小条,等等

2) If I change reverse property it changes constraints really very bad. I have extra strips and so on

3)我尝试了两种方法来将更改约束放在动画块之前和内部.没关系,可以一样

3) I tried both ways to put changing constraint before animation block and inside. It doesn't really matter, works the same

是否有任何帮助如何通过自动版式实现?或至少如何使用框架,但是我的视图控制器是基于自动布局的,因此无论如何我都会对此底视图有所限制.

Any help how to do that with autolayout? Or at least how to do it with frames but my view controller is based on autolayout, so anyway I will have constraints to this bottom view.

推荐答案

在对动画使用自动布局时,请按以下步骤操作:

When you are using autolayout for animations, you do it as follows:

  1. 确保完成自动布局:

  1. Make sure autolayout is done:

self.view.layoutIfNeeded()

  • 然后在动画块之前更改约束.例如:

  • Then you change the constraints BEFORE the animation block. So for example:

    someConstraint.constant = 0
    

  • 然后,在更改约束后,您告诉自动布局,约束已被更改:

  • Then after changing the constraint, you tell the autolayout that constraints have been changed:

    self.view.setNeedsLayout()
    

  • 然后您只需调用layoutIfNeeded()即可添加动画块:

  • And then you add an animation block with simply calling layoutIfNeeded():

    UIView.animate(withDuration: 1, animations: {
        self.view.layoutIfNeeded()
    })
    

  • 使用UIViewPropertyAnimator时也是如此-更改动画块中的约束.例如:

    The same applies when you use UIViewPropertyAnimator - change the constraints in the animation block. E.g.:

    self.view.layoutIfNeeded()
    someConstraint.constant = 0
    self.view.setNeedsLayout()
    let animator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {            
        self.view.layoutIfNeeded()
    }
    animator.startAnimation()
    

    之所以会这样,是因为layoutIfNeeded()进行了实际的布局-它计算了受影响视图的框架.因此,如果直接设置帧,则可以在动画块中进行设置.但是,自动布局"会为您设置帧-因此,您需要告诉自动布局在动画块中对其进行设置(如果要直接设置它们,您将这样做). layoutIfNeeded()调用恰好做到了-告诉自动布局引擎计算和设置新帧.

    This happens because layoutIfNeeded() does the actual layout - it calculates the frames of the affected views. So if you are setting frames directly, you set them in the animation block. However, Autolayout sets the frames for you - therefore what you need is to tell the autolayout to set them in the animation block (as you would do, if you would set them directly). The layoutIfNeeded() call does exactly that - it tells the autolayout engine to calculate and set the new frames.

    关于逆转:

    虽然我没有足够的经验可以百分百确定,但我希望仅将动画师设置为反转是不够的.因为您在开始动画之前应用了约束,然后只是告诉自动布局根据约束来更新帧-我认为当反转动画制作器时,您还需要也反转驱动动画的约束.

    While I don't have enough experience to be 100% sure, I would expect that simply setting the animator to reverse would not suffice. Since you apply the constraints before starting the animation, and then you just tell the autolayout to update frames according to the constraints - I would assume that when you reverse the animator, you would also need to reverse also the constraints that are driving the animation.

    Animator只是将视图动画化为新的帧.但是,无论是否反转,无论您是否反转动画师,新约束仍然有效.因此,在动画制作者完成后,如果以后的自动布局再次布局视图,我希望这些视图将进入当前活动约束所设置的位置.简而言之:动画师可以对帧变化进行动画处理,但不能对自身进行约束.这意味着反转动画师会反转帧,但不会反转约束-自动布局执行另一个布局周期后,就会再次应用它们.

    Animator just animates views into new frames. However, reversed or not, the new constraints still hold regardless of whether you reversed the animator or not. Therefore after the animator finishes, if later autolayout again lays out views, I would expect the views to go into places set by currently active constraints. Simply said: The animator animates frame changes, but not constraints themselves. That means reversing animator reverses frames, but it does not reverse constraints - as soon as autolayout does another layout cycle, they will be again applied.

    这篇关于UIViewPropertyAnimator问题与自动布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆