My app has two View Controllers (VC A & B) and a custom transition between them.
When using a left-directional pan gesture on VC A, an interactive animated transition modally presents VC B over A sliding in from the right (right-to-left). To dismiss VC B, user can:
UIPercentDrivenInteractiveTransition
object.Problem is that testing on Xcode 10 Seed (build 10A254a) + iOS 12 Simulator (X or XR or XS) I can easily get to a state where the custom transition never completes and the UI is left hanging in a weird state:
This problem never appeared on any previous version of Xcode and/or iOS pre Xcode 10/iOS12.
This is my animateTransition
method in my custom UIViewControllerAnimatedTransitioning
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from), let toVC = transitionContext.viewController(forKey: .to) else {
transitionContext.completeTransition(false)
return
}
let containterView = transitionContext.containerView
containterView.insertSubview(toVC.view, belowSubview: fromVC.view)
let bounds = fromVC.view.bounds
var xOffsetMultiplier : CGFloat = 0.0
var yOffsetMultiplier : CGFloat = 0.0
switch direction {
case .up:
yOffsetMultiplier = -1.0
case .right:
xOffsetMultiplier = 1.0
case .left:
xOffsetMultiplier = -1.0
case .down:
yOffsetMultiplier = 1.0
}
print(xOffsetMultiplier,bounds.size.width,bounds.size.height )
UIView.animate(withDuration: duration, animations: {
print("animating...")
//fromVC.navigationController?.navigationBar.alpha = 0.0
fromVC.view.frame = fromVC.view.frame.offsetBy(dx: xOffsetMultiplier * bounds.size.width, dy: yOffsetMultiplier * bounds.size.height)
}, completion: { finished in
print("completed animation")
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
//fromVC.navigationController?.navigationBar.alpha = 1.0
})
}
The prints are there just for debug.
This is the sequence that easily recreates the problem:
cancel()
on the UIPercentDrivenInteractiveTransition
object + I can verify the animation is completed. On a device I couldn't recreate this issue at all (yet) - and all transitions working as expected.
So found the source of why transitions where stuck between VC B and A but still don't really understand what's different between Xcode9/iOS11 and Xcode10/iOS12 that produced the different behaviour.
To keep it short:
UIPercentDrivenInteractiveTransition
, call dismiss(animated:completion:)
on the VC and update it according to the pan progress. In some cases, when the pan didn't traverse enough "ground" my gesture handler deems the transitions canceled and calls the cancel()
method of UIPercentDrivenInteractiveTransition
dismiss(animated:completion:)
but because the UIPercentDrivenInteractiveTransition
is still allocated it is returned by my transition delegate and the OS actually attempts an interactive dismissal although that wasn't the intent. This is a bug on my part as after calling cancel
I should also make sure the transition delegate doesn't attempt an interactive transition in this case (although on Xcode9/iOS11 it didn't).finish()
on the mistakenly allocated UIPercentDrivenInteractiveTransition
so it completes and everything is back to normal.Making sure the dismiss transition is either interactive or not based on the user interaction, especially after canceling an interactive one, fixed the problem.
What I don't understand is why isn't this consistent behaviour between Xcode/iOS versions. This problem never ever happened to me before on any device or simulator. There's something different in the way custom animations/transitions are handled - nothing in the Apple docs that could explain this - perhaps in the internal implementation of the transition context.
From a naive "eye-test" it seems that transition animations on the Xcode10 simulator are slower in reaction time and less smooth than before but still doesn't fully explain it.