I'm working on a UIPresentationController subclass similar to Mail.app's open draft behavior. When a view controller is presented, it doesn't go all the way to the top and the presenting view controller scales down as if it were falling back.
The basic gist of it is below:
class CustomPresentationController : UIPresentationController {
// Create a 40pt space above the view.
override func frameOfPresentedViewInContainerView() -> CGRect {
let frame = super.frameOfPresentedViewInContainerView()
let insets = UIEdgeInsets(top: 40, left: 0, bottom: 0, right: 0)
return UIEdgeInsetsInsetRect(frame, insets)
}
// Scale down when expanded is true, otherwise identity.
private func setScale(expanded expanded: Bool) {
if expanded {
let fromMeasurement = presentingViewController.view.bounds.width
let fromScale = (fromMeasurement - 30) / fromMeasurement
presentingViewController.view.transform = CGAffineTransformMakeScale(fromScale, fromScale)
} else {
presentingViewController.view.transform = CGAffineTransformIdentity
}
}
// Scale down alongside the presentation.
override func presentationTransitionWillBegin() {
presentingViewController.transitionCoordinator()?.animateAlongsideTransition({ context in
self.setScale(expanded: true)
}, completion: { context in
self.setScale(expanded: !context.isCancelled())
})
}
// Scale up alongside the dismissal.
override func dismissalTransitionWillBegin() {
presentingViewController.transitionCoordinator()?.animateAlongsideTransition({ context in
self.setScale(expanded: false)
}, completion: { context in
self.setScale(expanded: context.isCancelled())
})
}
// Fix the scaled view's frame on orientation change.
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
guard let bounds = containerView?.bounds else { return }
presentingViewController.view.bounds = bounds
}
}
This works fine for a non-interactive presentation or dismissal. When performing an interactive dismissal, however, all animations on presentingViewController.view run non-interactively. That is, the scaling will happen in the ~300ms it normally takes to dismiss rather than staying at 3% complete when 3% dismissed.
You can see this in a sample project is available on GitHub. and a video of the issue is on YouTube.
I've tried the following approaches but they all yield the same result:
The problem was that presentingViewController is not a descendent of the presentation's containerView. UIPercentDrivenInteractiveTransition works by setting containerView.layer.speed to zero and setting containerView.layer.timeOffset to reflect the percent complete. Since the view in question was not part of the hierarchy, its speed stayed at 1 and it completed as normal.
This is explicitly stated in the documentation for animateAlongsideTransition(_:,completion:):
Use this method to perform animations that are not handled by the animator objects themselves. All of the animations you specify must occur inside the animation context’s container view (or one of its descendants). Use the containerView property of the context object to get the container view. To perform animations in a view that does not descend from the container view, use the
animateAlongsideTransitionInView:animation:completion:method instead.
As the documentation indicates, switching to animateAlongsideTransitionInView(_:,animation:,completion:) fixes the problem:
// Scale up alongside the dismissal.
override func dismissalTransitionWillBegin() {
presentingViewController.transitionCoordinator()?.animateAlongsideTransitionInView(presentingViewController.view, animation: { context in
self.setScale(expanded: false)
}, completion: { context in
self.setScale(expanded: context.isCancelled())
})
}
The comment on that method in the header is a lot more direct about this than the documentation:
// This alternative API is needed if the view is not a descendent of the container view AND you require this animation to be driven by a UIPercentDrivenInteractiveTransition interaction controller.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With