I have a small custom view controller (it's a menu) I'm presenting, and I want you to be able to halt the presentation half way through if you change your mind.
Per this WWDC 2016 video, doing so should be pretty easy and just require the implementation of interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating
, and return, say a UIViewPropertyAnimator
.
However I'm not finding this to be the case. If I return my property animator there, and programmatically call my view controller to be dismissed half way through the transition, it waits until the animation is fully concluded for the dismiss animation to occur. It won't interrupt.
*Note: this is with a "normal" animation controller, not an interactive one. Per the video this should be fine, it's not inherently an interactive transition"
I set up my corresponding controllers as shown:
```swift func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return MyMenuAnimationController(type: .presentation) }
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return MyMenuAnimationController(type: .dismissal) }
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return MyMenuPresentationController(presentedViewController: presented, presenting: presenting) } ```
And the specified animation controller (as mentioned there's no interaction controller as there's no gesture recognizer or anything attached to this transition, it only fires on tap):
```swift class MyMenuAnimationController: NSObject, UIViewControllerAnimatedTransitioning { enum AnimationControllerType { case presentation, dismissal }
let type: AnimationControllerType
var animatorForCurrentSession: UIViewPropertyAnimator?
init(type: AnimationControllerType) {
self.type = type
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 5.0 // Artificially long for testing
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if type == .presentation, let myMenuMenu: MyMenuMenu = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? MyMenuMenu {
transitionContext.containerView.addSubview(myMenuMenu.view)
}
interruptibleAnimator(using: transitionContext).startAnimation()
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
if let animatorForCurrentSession = animatorForCurrentSession {
return animatorForCurrentSession
}
let propertyAnimator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), dampingRatio: 0.75)
propertyAnimator.isInterruptible = true
propertyAnimator.isUserInteractionEnabled = true
let isPresenting = type == .presentation
guard let myMenuMenu: MyMenuMenu = {
return (isPresenting ? transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) : transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)) as? MyMenuMenu
}() else {
preconditionFailure("Menu should be accessible")
}
myMenuMenu.view.frame = transitionContext.finalFrame(for: myMenuMenu)
let initialAlpha: CGFloat = isPresenting ? 0.0 : 1.0
let finalAlpha: CGFloat = isPresenting ? 1.0 : 0.0
myMenuMenu.view.alpha = initialAlpha
propertyAnimator.addAnimations {
myMenuMenu.view.alpha = finalAlpha
}
propertyAnimator.addCompletion { (position) in
guard position == .end else { return }
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
self.animatorForCurrentSession = nil
}
self.animatorForCurrentSession = propertyAnimator
return propertyAnimator
}
private func calculateTranslationRequired(forMyMenuMenuFrame myMenuMenuFrame: CGRect, toDesiredPoint desiredPoint: CGPoint) -> CGVector {
let centerPointOfMenuView = CGPoint(x: myMenuMenuFrame.origin.x (myMenuMenuFrame.width / 2.0), y: myMenuMenuFrame.origin.y (myMenuMenuFrame.height / 2.0))
let translationRequired = CGVector(dx: desiredPoint.x - centerPointOfMenuView.x, dy: desiredPoint.y - centerPointOfMenuView.y)
return translationRequired
}
} ```
And a simple presentation controller:
swift
class MyMenuPresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
return CGRect(x: 100, y: 100, width: 100, height: 100)
}
}
But as I said, if I present it simply and then 1 second into the 5 second animation I dismiss it (signifying someone tapping outside the view controller to dismiss it mid transition), nothing occurs until the animation is done. It's as if it's queued up, but won't execute until the presentation is completed.
```swift let myMenuMenu = MyMenuMenu() present(myMenuMenu, animated: true, completion: nil)
DispatchQueue.main.asyncAfter(deadline: .now() .seconds(1)) { myMenuMenu.dismiss(animated: true, completion: nil) } ```
Am I misunderstanding a core part of the custom view controller transition flow? Do I have to make it an interactive transition? Something else? In the event that an interactive transition is required, where do I update the interaction controller in my code with progress? With a gesture it's obvious, but this is just a "fade 0 alpha to 1 alpha" animation.
Subreddit
Post Details
- Posted
- 3 years ago
- Reddit URL
- View post on reddit.com
- External URL
- reddit.com/r/iOSProgramm...