Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Cancellable Interaction for Presentation Animation #1

Merged
merged 6 commits into from Mar 11, 2021

Conversation

TimOliver
Copy link
Contributor

Hey @christianselig!

As promised, I had a bit of a play with making the opening presentation animation and I got it working! :D

Here's the things I changed:

  • I modified ChidoriAnimationController to also conform to the interactive controller protocol, and modified ChidoriMenu to retain and provide the same instance to both animation and interactive delegates.
  • I included a public cancel method that retains the context of the animation to allow cancellations, and also stops, and then restarts the "opening" animation in reverse.
  • When a presentation starts, it is treated as interactive from the start, meaning touches automatically work by default.
  • When the user taps the dimming view, this now forwards the tap through a delegate pattern to ChidoriMenu
  • Chidori menu then calls the cancellation method, but if the animation has already completed, it also calls dismiss to dismiss it as normal.

I've also included a small Xcode project in order to make it super quick to test this out. I'd also recommend adding CocoaPods and Swift Package Manager support (or if you'd like, I can do that).

Please try it out, and let me know what you think. There might be some better ways to streamline this logic and code organization, but this should hopefully be a good start.

All the best!

@christianselig
Copy link
Owner

This looks awesome! Thank you so much! Very clean and I learned a bunch reading through this, it's not as scary as it seems apparently. 😛

One catch probably worth adding, is that when it interrupts in this way my way of making the tintAdjustmentMode controlled via the UIPresentationController subclass doesn't really work so much anymore as an actual dismissal never occurs.

In ChidoriAnimationController could you add something like this? Seems to take care of the tintAdjustment nicely. 😀

func cancelTransition() {
  guard let context = context,
        let animator = animatorForCurrentSession else { return }

   // Cancel the current transition
  context.cancelInteractiveTransition()

   // Play the animation in reverse
  animator.pauseAnimation()
  animator.isReversed = true
  animator.startAnimation()
  
  if type == .presentation {
      if let presentingViewController = context.viewController(forKey: .from) {
          presentingViewController.view.tintAdjustmentMode = .automatic
      } else {
          preconditionFailure("Presenting view controller should be accessible")
      }
  }
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
  let interruptableAnimator = interruptibleAnimator(using: transitionContext)
  
  if type == .presentation {
      if let chidoriMenu: ChidoriMenu = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? ChidoriMenu {
          transitionContext.containerView.addSubview(chidoriMenu.view)
      }
      
      if let presentingViewController = transitionContext.viewController(forKey: .from) {
          presentingViewController.view.tintAdjustmentMode = .dimmed
      } else {
          preconditionFailure("Presenting view controller should be accessible")
      }
  } else {
      if let presentingViewController = transitionContext.viewController(forKey: .to) {
          presentingViewController.view.tintAdjustmentMode = .automatic
      }
  }
  
  interruptableAnimator.startAnimation()
}

@christianselig
Copy link
Owner

Oh! And two questions if you don't mind:

In this block:

 // Play the animation in reverse
animator.pauseAnimation()
animator.isReversed = true
animator.startAnimation()

Is the pause + start required?

And with this solution, what even is interruptibleAnimator() doing? (It wasn't doing much in mine either!) This API confuses me, it seems like we're doing everything manually, I'm so confused what the interruptibleAnimator method brings to the table, haha.

@TimOliver
Copy link
Contributor Author

TimOliver commented Feb 24, 2021

My pleasure! Awesome! I'm glad to hear it helped you learn more about how this crazy API works! Certainly if you make the same object conform to both the animation and interaction protocols, it's actually not as complicated as you might imagine since a lot of the logic overlaps. I actually originally learned the gist of how this all works from this repo if that helps.

In ChidoriAnimationController could you add something like this? Seems to take care of the tintAdjustment nicely.

Done! Hmmm, that being said. I wonder if having to add this of logic in implies there might be a better way to do this that does actually capture the reverse animation as a proper dismissal. Let me know if you're okay with this. :)

Is the pause + start required?

Huh! I actually didn't know! I assumed it would require really explicit commands. I just tested it, and I was able to remove pauseAnimation(), but if I remove startAnimation() then the animation doesn't reverse at all anymore.

And with this solution, what even is interruptibleAnimator() doing? (It wasn't doing much in mine either!)

Hahah I know right! I'm not even sure either! I set a breakpoint to double check that that delegate method even gets called, and it certainly does. My best (completely uneducated) guess is that any auxiliary animations (Like the background overlay view fading in) are wrapped into the same animation context with it. But apart from that, I couldn't see any specific behavior from the system by specifically providing the animator object to it (This might be a good question for the WWDC labs. If those ever come back!).

Anyway, I've added the changes you've requested. Please try it again and let me know if you need anything else changed!

Take it easy!

@christianselig christianselig merged commit 28459e6 into christianselig:main Mar 11, 2021
@christianselig
Copy link
Owner

Oh my gosh I'm sorry I'm so bad at GitHub I didn't see your reply until now. ;_;

My pleasure! Awesome! I'm glad to hear it helped you learn more about how this crazy API works! Certainly if you make the same object conform to both the animation and interaction protocols, it's actually not as complicated as you might imagine since a lot of the logic overlaps. I actually originally learned the gist of how this all works from this repo if that helps.

Whoaaaaa, I love a good view controller transition article plus repo, thank you, that's look like a great resource!

Done! Hmmm, that being said. I wonder if having to add this of logic in implies there might be a better way to do this that does actually capture the reverse animation as a proper dismissal. Let me know if you're okay with this. :)

Probably, coupled with your point about interruptibleAnimator I'd really love to poke a UIKit engineer's brain about this stuff, a lab would be awesome, fingers crossed.

Thank you so much again for doing this, I very much appreciate it! Merged!

@TimOliver
Copy link
Contributor Author

Haha no worries! I do the same with my repos too all the time. 😅 Awesome! Glad to hear that was all good with you!

It was my pleasure! If you find any other issues related to it, feel free to hit my up anytime! All the best! :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants