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:
- Use right-directional pan gesture: will trigger an interactive transition that will slide VC B back to the right and uncover VC A. The position of VC B is determined interactively by the pan gesture state. The interaction is 'driven' by a
UIPercentDrivenInteractiveTransition
object. - Use a "close" button on VC B navbar. This will trigger the custom transition (slide to right) with no interaction (just animated).
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:
- UI is stuck on VC B and no gestures or taps work.
- The app isn't stuck - I can see conosle logs still rolling and netwrok activity is working (no errors in the log)
- Pausing the app in this stuck state I can see com.apple.main-thread is not stuck.
- When I hit "Debug View Hierarchy" something weird happens: on sim screen I can still see VC B and all UI is disabled. On view debugger main view - I can see VC A's subviews drawn as if the transition is done. On view debugger left tree view - I can see the view hierarcgy of VC B.
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:
- Use pan gesture to start interactive transition from B back to A but never complete it - this will call
cancel()
on theUIPercentDrivenInteractiveTransition
object + I can verify the animation is completed. - Tap the 'close' button to invoke the non-interactive transition to dismiss B. B never dismisses and the custom animation never completes!
On a device I couldn't recreate this issue at all (yet) - and all transitions working as expected.