Reputation: 4302
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?
Upvotes: 8
Views: 1060
Reputation: 3783
In my case, It works with interruptibleAnimator(using:)
by return UIViewPropertyAnimator
Upvotes: 1
Reputation: 1092
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...
})
Upvotes: 2
Reputation: 535231
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`
Upvotes: 2