UIViewPropertyAnimator different behaviour on iOS10 and iOS11 reversing an animation. Bug on `isReversed` and `fractionComplete` properties?

后端 未结 3 1840
你的背包
你的背包 2021-02-06 00:16

THE PROBLEM
Running the same code on iOS10 and iOS11 my UIViewPropertyAnimator has a different behaviour just after changing of his .isReversed

相关标签:
3条回答
  • 2021-02-06 00:30
    1. On iOS 11, fractionComplete will be reversed (that is, 1 - originalFractionComplete) after you reverse animation by animator.isReversed = true.

    2. Spring animation that have less than 0.1s duration will complete instantly.

    So you may originally want the reversed animation runs 90% of the entire animation duration, but on iOS 11, the reversed animation actually runs 10% duration because isReversed changed, and that 10% duration is less than 0.1s, so the animation will be completed instantly and looks like no animation happened.

    How to fix?

    For iOS 10 backward compatibility, copy the fractionComplete value before you reverse animation and use it for continueAnimation.

    e.g.

    let fraction = animator.fractionComplete
    animator.isReversed = true
    animator.continueAnimation(...*fraction*...)
    
    0 讨论(0)
  • 2021-02-06 00:35

    I have been fighting this for quite a while as well, but then I noticed the scrubsLinearly property that was added to UIViewPropertyAnimator in iOS 11:

    Defaults to true. Provides the ability for an animator to pause and scrub either linearly or using the animator’s current timing.

    Note that the default of this property is true, which seems to cause a conflict with using a non-linear animation curve. That might also explain why the issue is not present when using a linear timing function.

    Setting scrubsLinearly to false, the animator seems to work as expected:

    let animator = UIViewPropertyAnimator(duration: 0.25, curve: .easeOut) {
       ...
    }
    animator.scrubsLinearly = false
    
    0 讨论(0)
  • 2021-02-06 00:43

    I have tried many solutions, but no one didn't work for me. I wrote my solution and everything is fine now. My solution:

    1. take an image of the screen and show it

    2. finish animation

    3. start new animation for old state

    4. pause the animation and set progress (1 - original progress)

    5. remove screen image and continue animation

      switch pan.state {
      ...
      case .ended, .cancelled, .failed:
          let velocity = pan.velocity(in: view)
          let reversed: Bool
      
          if abs(velocity.y) < 200 {
              reversed = progress < 0.5
          } else {
              switch state {
              case .shown:
                  reversed = velocity.y < 0
              case .minimized:
                  reversed = velocity.y > 0
              }
          }
      
          if reversed {
              let overlayView = UIScreen.main.snapshotView(afterScreenUpdates: false)
              view.addSubview(overlayView)
              animator?.stopAnimation(false)
              animator?.finishAnimation(at: .end)
              startAnimation(state: state.opposite)
              animator?.pauseAnimation()
              animator?.fractionComplete = 1 - progress
              overlayView.removeFromSuperview()
              animator?.continueAnimation(withTimingParameters: nil, durationFactor: 0.5)
          } else {
              animator?.continueAnimation(withTimingParameters: nil, durationFactor: 0)
          }
      

    And the animation curve option must be linear.

        animator = UIViewPropertyAnimator(duration: 0.3, curve: .linear) {
            startAnimation()
        }
    
    0 讨论(0)
提交回复
热议问题