7
votes

I'm playing around with custom and interactive view controller transistion, with UIPercentDrivenInteractiveTransition. I'm building an app that present a card (other view controller) modally. I've made custom transistions with UIViewControllerAnimatedTransitioning that is animating the card view controller a lot like the standart model presentation style.

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    {...}
    let fullScreenFrame = transitionContext.finalFrame(for: cardVC)
    let offsetFrame = cardVC.view.frame.offsetBy(dx: 0, dy: cardVC.view.frame.height)
    let finalFrame = presenting ? fullScreenFrame : offsetFrame
    cardVC.view.frame = presenting ? offsetFrame : fullScreenFrame
    containerView.addSubview(cardVC.view)

    UIView.animateKeyframes(
        withDuration: transitionDuration(using: transitionContext),
        delay: 0,
        options: UIViewKeyframeAnimationOptions.calculationModeLinear,
        animations: {
            UIView.addKeyframe(
                withRelativeStartTime: 0,
                relativeDuration: 1,
                animations: { [weak self] in
                    guard let strongSelf = self else { return }
                    backgroundView.alpha = strongSelf.presenting ? 1 : 0
            })

            UIView.addKeyframe(
                withRelativeStartTime: 0,
                relativeDuration: 1,
                animations: {
                    cardVC.view.frame = finalFrame
            })
    }, completion: { finished in
        backgroundView.removeFromSuperview()
        gradientView.alpha = 1
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
    })
}

And then I use a pan gesture recognizer to interactively drive the dismiss animation:

func handleGesture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) {
        let translation = gestureRecognizer.translation(in: gestureRecognizer.view)
        var progress = (translation.y / (UIScreen.main.bounds.height - 70))
        progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))

        switch gestureRecognizer.state {
        case .began:
            interactionInProgress = true
            viewController.dismiss(animated: true, completion: nil)

        case .changed:
            shouldCompleteTransition = progress > 0.35
            update(progress)

        case .cancelled:
            interactionInProgress = false
            cancel()

        case .ended:
            interactionInProgress = false
            if !shouldCompleteTransition {
                cancel()
            } else {
                finish()
            }

        default:
            print("Unsupported")
        }
    }

When I dismiss the presented view controller by dragging it down, it doesn't seem to move linearly with the gesture. It seems like the Interaction Controller is using some easeInEaseOut function. Maybe setting UIPercentDrivenInteractiveTransition's timingCurve to make the transition run linearly. Is that posible, or am I getting something wrong?

3

3 Answers

2
votes

I was having the same issue, was able to set a custom animation curve for animateKeyframes using UIView.setAnimationCurve like this:

UIView.animateKeyframes(withDuration: 4, delay: 0, options: [], animations: {

    UIView.setAnimationCurve(.linear)

    UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.24, animations: {
        // Animations...
    })

    // Other key frames...
})
0
votes

You yourself have given the animation a default ease in - ease out timing curve, by not setting the timing curve to anything different in your call to UIView.animateKeyframes. That applies even during interaction.

Note that setting the animation's options to UIViewKeyframeAnimationOptions.calculationModeLinear does not change the timing curve (in case that's what you thought you were accomplishing here). The way to add a linear timing curve to a linear calculation mode keyframe animation is like this:

var opts : UIViewKeyframeAnimationOptions = .calculationModeLinear
let opt2 : UIViewAnimationOptions = .curveLinear
opts.insert(UIViewKeyframeAnimationOptions(rawValue:opt2.rawValue))
// and now use `opts` as your `options`
0
votes

In my case, It works with interruptibleAnimator(using:) by return UIViewPropertyAnimator