UIPercentDrivenInteractiveTransition Cancelling Issue - ios

What I Have
I am using UIViewControllerAnimatedTransitioning protocol with an attached UIViewPropertyAnimator to pan down to dismiss a View Controller
extension SecondViewController : UIViewControllerAnimatedTransitioning {
func interruptibleAnimator(using ctx: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
if self.animator != nil {
return self.animator!
}
let containerView = ctx.containerView
let toVC = ctx.viewController(forKey: .to) as! FirstViewController
let fromVC = ctx.viewController(forKey: .from) as! SecondViewController
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
self.animator = UIViewPropertyAnimator(duration: transitionDuration(using: ctx),
curve: .easeOut, animations: {
self.fromVC.view.transform = CGAffineTransform(scale: 0.5)
})
self.animator.isInterruptible = true
self.animator.isUserInteractionEnabled = true
self.animator.isManualHitTestingEnabled = true
self.animator.addCompletion { position in
switch position {
case .end:
break
case .current:
break
case .start:
break
}
let cancelled = ctx.transitionWasCancelled
if (cancelled) {
//..
} else {
//..
}
ctx.completeTransition(!cancelled)
}
self.animator = anim
return self.animator
}
func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
let animator = self.interruptibleAnimator(using: ctx)
self.animator.startAnimation()
}
func animationEnded(_ transitionCompleted: Bool) {
self.interactiveTransition = nil
self.animator = nil
}
}
Pan Gesture to handle the animation:
func handlePanGesture(gestureRecognizer: UIPanGestureRecognizer) {
let panTranslation = gestureRecognizer.translation(in: gestureRecognizer.view!)
var progress = panTranslation.y / (gestureRecognizer.view!.bounds.size.height * 0.5)
switch gestureRecognizer.state {
case .began:
self.interactiveTransition = UIPercentDrivenInteractiveTransition()
self.navigationController!.popViewController(animated: true)
case .changed:
self.interactiveTransition!.update(progress)
case .cancelled, .ended:
if progress > 0.5 {
//Complete Transition
let timingParameters = UICubicTimingParameters(animationCurve: .easeInOut)
self.animator!.continueAnimation!(withTimingParameters: timingParameters, durationFactor: progress)
self.animator?.addAnimations! {
//Completion Animations
}
self.interactiveTransition!.finish()
} else {
//Cancel Transition
self.animator!.isReversed = true
let timingParameters = UICubicTimingParameters(animationCurve: .easeInOut)
self.animator!.continueAnimation!(withTimingParameters: timingParameters, durationFactor: progress)
self.animator!.addAnimations!({
//Cancelling Animations
}, delayFactor: 0 )
self.interactiveTransition!.cancel()
}
default:
break
}
}
What Works
Swiping down to dismissal works perfectly. Swiping slightly down and lifting finger to cancel also works perfectly.
Issue
Swiping down and back up beyond starting point (where progress becomes negative) and lifting up the finger should cancel the transition with cancelling animation. This happens in iOS 10 but it first reverses the navigation controller transitions first, then snaps back. In iOS 11, cancelling animation happens, then I see navigation controller transition is reversed. If you wait, you can see navigation controller transition does try to correct it self in animation over 10 mins or so.
Issue with:
- self.interactiveTransition!.cancel()?
- self.interactiveTransition!.completionSpeed ??

I don't know if this is a bug or we're all just doing it wrong but to correct the behavior, add .completionSpeed = 0.999 to the interactionController in the .ended case of the pan gesture handler. It's a hack but at least it's only a single line.

Related

Interactive sliding menu goes to completion as soon as pan gesture starts [Swift]

I am trying to implement a sliding menu that can be interactively dismissed by horizontal panning, same as the ones in Uber and Google apps. Everything works as expected except that, as soon as I start panning horizontally, dismiss goes to completion without following my finger. Any suggestion of where the problem may lie is very appreciated.
I subclassed UIPresentationController to define the presented width of my menu controller. I have custom presentation animator and dismiss animator, and a UIViewControllerTransitioningDelegate object to return them all to UIKit. I also implemented gestureRecognizerShouldBegin(_ gestureRecognizer:) method in my menu controller to allow vertical scrolling.
SlideDismissAnimator
class SlideDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let interactionController: SlideInteractionController?
init(interactionController: SlideInteractionController?) {
self.interactionController = interactionController
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromCV = transitionContext.viewController(forKey: .from)!
let initialFrame = transitionContext.finalFrame(for: fromCV)
var finalFrame = initialFrame
finalFrame.origin.x = transitionContext.containerView.frame.width // My menu slides in from right
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: {
fromCV.view.frame = finalFrame
}) { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
SlideInteractionController
class SlideInteractionController: UIPercentDrivenInteractiveTransition {
var interactionInProgress = false
private var shouldCompleteTransition = false
private weak var collectionViewController: UICollectionViewController!
init(collectionViewController: UICollectionViewController) {
super.init()
self.collectionViewController = collectionViewController
if let menuController = collectionViewController as? MenuController {
let gesture = UIPanGestureRecognizer(target: self, action: #selector(handleGesture))
menuController.collectionView?.addGestureRecognizer(gesture)
gesture.delegate = menuController
}
}
#objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: gestureRecognizer.view!.superview!)
var progress = (translation.x / 100)
progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))
switch gestureRecognizer.state {
case .began:
interactionInProgress = true
collectionViewController.dismiss(animated: true, completion: nil)
case .changed:
shouldCompleteTransition = progress > 0.5
update(progress)
case .cancelled:
interactionInProgress = false
cancel()
case .ended:
interactionInProgress = false
if shouldCompleteTransition {
finish()
} else {
cancel()
}
default:
break
}
}
}
MenuController
class MenuController: UICollectionViewController, UIGestureRecognizerDelegate {
var slideInteractionController: SlideInteractionController?
override func viewDidLoad() {
super.viewDidLoad()
setupView()
slideInteractionController = SlideInteractionController(collectionViewController: self)
}
...
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer {
let translation = panGestureRecognizer.translation(in: collectionView)
if translation.x > fabs(translation.y) {
return true
}
}
return false
}
}
I made a sample Project that Use a tableView inside a View that acts as A side Menu drawer Presented over Current Context
my pan Handler
//MARK: Pan gesture Handler
#objc func handlePanGesture(panGesture: UIPanGestureRecognizer)
{
///Get the changes
let translation = panGesture.translation(in: self.view)
///Make View move to left side of Frame
if CGFloat(round(Double((panGesture.view?.frame.origin.x)!))) <= 0
{
panGesture.view!.center = CGPoint(x: panGesture.view!.center.x + translation.x, y: panGesture.view!.center.y)
panGesture.setTranslation(CGPoint.zero, in: self.view)
}
///Do not let View go beyond origin as 0
if CGFloat(round(Double((panGesture.view?.frame.origin.x)!))) > 0
{
panGesture.view?.frame.origin.x = 0
panGesture.setTranslation(CGPoint.zero, in: self.view)
}
///States When Dragging
switch panGesture.state
{
case .changed:
self.setAlphaOfBlurView(origin: (panGesture.view?.frame.maxX)!)
case .ended:
if CGFloat(round(Double((panGesture.view?.frame.maxX)!))) >= self.view.frame.size.width*0.35
{
UIView.animate(withDuration: 0.7, animations: {
panGesture.view?.frame.origin.x = 0
panGesture.setTranslation(CGPoint.zero, in: self.view)
})
}
else
{
UIView.animate(withDuration: 0.4, animations: {
panGesture.view?.frame.origin.x -= self.maximum_x
panGesture.setTranslation(CGPoint.zero, in: self.view)
}, completion: { (success) in
if (success)
{
self.remove(asChildViewController: self.sideMenuVCObject, baseView: self.baseView)
self.baseView.removeFromSuperview()
self.blurView.removeFromSuperview()
//Remove Notification observer
NotificationCenter.default.removeObserver(self,name: NSNotification.Name(rawValue: "hideMenu"),object: nil)
}
})
}
break
default:
print("Default Case")
}
}
Repository Link at GiHub
https://github.com/RockinGarg/Slide-Menu-Drawer.git
Working Video :
https://drive.google.com/open?id=13Q-bBkVlAX7uEweDyQGvNct-dXkBSveT

How can I create a working interruptible view controller transition on iOS?

iOS 10 added a new function for custom animated view controller transitions called
interruptibleAnimator(using:)
Lots of people appear to be using the new function, however by simply implementing their old animateTransition(using:) within the animation block of a UIViewPropertyAnimator in interruptibleAnimator(using:) (see Session 216 from 2016)
However I can't find a single example of someone actually using the interruptible animator for creating interruptible transitions. Everyone seems to support it, but no one actually uses it.
For example, I created a custom transition between two UIViewControllers using a UIPanGestureRecognizer. Both view controllers have a backgroundColor set, and a UIButton in the middle that changes the backgroundColour on touchUpInside.
Now I've implemented the animation simply as:
Setup the toViewController.view to be positioned to the
left/right (depending on the direction needed) of the
fromViewController.view
In the UIViewPropertyAnimator animation block, I slide the
toViewController.view into view, and the fromViewController.view out
of view (off screen).
Now, during transition, I want to be able to press that UIButton. However, the button press was not called. Odd, this is how the session implied things should work, I setup a custom UIView to be the view of both of my UIViewControllers as follows:
class HitTestView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
if view is UIButton {
print("hit button, point: \(point)")
}
return view
}
}
class ViewController: UIViewController {
let button = UIButton(type: .custom)
override func loadView() {
self.view = HitTestView(frame: UIScreen.main.bounds)
}
<...>
}
and logged out the func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? results. The UIButton is being hitTested, however, the buttons action is not called.
Has anyone gotten this working?
Am I thinking about this wrong and are interruptible transitions just to pausing/resuming a transition animation, and not for interaction?
Almost all of iOS11 uses what I believe are interruptible transitions, allowing you to, for example, pull up control centre 50% of the way and interact with it without releasing the control centre pane then sliding it back down. This is exactly what I wish to do.
Thanks in advance! Spent way to long this summer trying to get this working, or finding someone else trying to do the same.
I have published sample code and a reusable framework that demonstrates interruptible view controller animation transitions. It's called PullTransition and it makes it easy to either dismiss or pop a view controller simply by swiping downward. Please let me know if the documentation needs improvement. I hope this helps!
Here you go! A short example of an interruptible transition. Add your own animations in the addAnimation block to get things going.
class ViewController: UIViewController {
var dismissAnimation: DismissalObject?
override func viewDidLoad() {
super.viewDidLoad()
self.modalPresentationStyle = .custom
self.transitioningDelegate = self
dismissAnimation = DismissalObject(viewController: self)
}
}
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return dismissAnimation
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
guard let animator = animator as? DismissalObject else { return nil }
return animator
}
}
class DismissalObject: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning {
fileprivate var shouldCompleteTransition = false
var panGestureRecongnizer: UIPanGestureRecognizer!
weak var viewController: UIViewController!
fileprivate var propertyAnimator: UIViewPropertyAnimator?
var startProgress: CGFloat = 0.0
var initiallyInteractive = false
var wantsInteractiveStart: Bool {
return initiallyInteractive
}
init(viewController: UIViewController) {
self.viewController = viewController
super.init()
panGestureRecongnizer = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
viewController.view.addGestureRecognizer(panGestureRecongnizer)
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 8.0 // slow animation for debugging
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {}
func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
let animator = interruptibleAnimator(using: transitionContext)
if transitionContext.isInteractive {
animator.pauseAnimation()
} else {
animator.startAnimation()
}
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
// as per documentation, we need to return existing animator
// for ongoing transition
if let propertyAnimator = propertyAnimator {
return propertyAnimator
}
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to)
else { fatalError("fromVC or toVC not found") }
let containerView = transitionContext.containerView
// Do prep work for animations
let duration = transitionDuration(using: transitionContext)
let timingParameters = UICubicTimingParameters(animationCurve: .easeOut)
let animator = UIViewPropertyAnimator(duration: duration, timingParameters: timingParameters)
animator.addAnimations {
// animations
}
animator.addCompletion { [weak self] (position) in
let didComplete = position == .end
if !didComplete {
// transition was cancelled
}
transitionContext.completeTransition(didComplete)
self?.startProgress = 0
self?.propertyAnimator = nil
self?.initiallyInteractive = false
}
self.propertyAnimator = animator
return animator
}
#objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
switch gestureRecognizer.state {
case .began:
initiallyInteractive = true
if !viewController.isBeingDismissed {
viewController.dismiss(animated: true, completion: nil)
} else {
propertyAnimator?.pauseAnimation()
propertyAnimator?.isReversed = false
startProgress = propertyAnimator?.fractionComplete ?? 0.0
}
break
case .changed:
let translation = gestureRecognizer.translation(in: nil)
var progress: CGFloat = translation.y / UIScreen.main.bounds.height
progress = CGFloat(fminf(fmaxf(Float(progress), -1.0), 1.0))
let velocity = gestureRecognizer.velocity(in: nil)
shouldCompleteTransition = progress > 0.3 || velocity.y > 450
propertyAnimator?.fractionComplete = progress + startProgress
break
case .ended:
if shouldCompleteTransition {
propertyAnimator?.startAnimation()
} else {
propertyAnimator?.isReversed = true
propertyAnimator?.startAnimation()
}
break
case .cancelled:
propertyAnimator?.isReversed = true
propertyAnimator?.startAnimation()
break
default:
break
}
}
}

iOS Interactive Animation Swift

I cannot for the life understand why this is not working.
Have two VCs: A and B.
I want to swipe right on VC A to reveal VC B but want to make it interactive so that a user can drag between two VCs (like on Instagram home screen when you swipe left and right to go to the Camera and messages). At the moment, it doesn't 'drag'. You can swipe on VC A and it will go to VC B.
Here's my animator object to slide right:
class SlideRightTransitionAnimator: NSObject {
let duration = 0.5
var isPresenting = false
let customInteractiveTransition = CustomInteractiveTransition()
}
// MARK: UIViewControllerTransitioningDelegate
extension SlideRightTransitionAnimator: UIViewControllerTransitioningDelegate {
// Return the animator when presenting a VC
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = true
return self
}
// Return the animator used when dismissing from a VC
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
isPresenting = false
return self
}
// Add the interactive transition for Presentation only
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return customInteractiveTransition.transitionInProgress ? customInteractiveTransition : nil
}
}
// MARK: UIViewControllerTransitioningDelegate
extension SlideRightTransitionAnimator: UIViewControllerAnimatedTransitioning {
// Return how many seconds the transiton animation will take
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
// Animate a change from one VC to another
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Get reference to our fromView, toView and the container view that we should perform the transition
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
// Set up the transform we'll use in the animation
let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)
let offScreenLeft = CGAffineTransform(translationX: -container.frame.width, y: 0)
// Start the toView to the right of the screen
if isPresenting {
toView.transform = offScreenLeft
}
// Add the both views to our VC
container.addSubview(toView)
container.addSubview(fromView)
// Perform the animation
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: [], animations: {
if self.isPresenting {
fromView.transform = offScreenRight
toView.transform = CGAffineTransform.identity
}
else {
fromView.transform = offScreenLeft
toView.transform = CGAffineTransform.identity
}
}, completion: { finished in
// Tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
}
Here's my Interactive Transition Controller
class CustomInteractiveTransition: UIPercentDrivenInteractiveTransition {
weak var viewController : UIViewController!
var shouldCompleteTransition = false
var transitionInProgress = false
var completionSeed: CGFloat {
return 1 - percentComplete
}
func attachToViewController(viewController: UIViewController) {
self.viewController = viewController
setupPanGestureRecognizer(view: viewController.view)
}
private func setupPanGestureRecognizer(view: UIView) {
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture)))
}
func handlePanGesture(gestureRecognizer: UIPanGestureRecognizer) {
let viewTranslation = gestureRecognizer.translation(in: gestureRecognizer.view!.superview!)
var progress = (viewTranslation.x / 200)
progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))
switch gestureRecognizer.state {
case .began:
transitionInProgress = true
viewController.dismiss(animated: true, completion: nil)
case .changed:
shouldCompleteTransition = progress > 0.5
update(progress)
case .cancelled, .ended:
transitionInProgress = false
if !shouldCompleteTransition || gestureRecognizer.state == .cancelled {
cancel()
} else {
finish()
}
default:
print("Swift switch must be exhaustive, thus the default")
}
}
}
And lastly the code for VC A:
class ViewControllerA: UITableViewController {
let slideRightTransition = SlideRightTransitionAnimator()
let customInteractiveTransition = CustomInteractiveTransition()
override func viewDidLoad() {
super.viewDidLoad()
// Add a Pan Gesture to swipe to other VC
let swipeGestureRight = UISwipeGestureRecognizer(target: self, action: #selector(swipeGestureRightAction))
swipeGestureRight.direction = .right
view.addGestureRecognizer(swipeGestureRight)
}
// MARK: Pan gestures
func swipeGestureRightAction() {
performSegue(withIdentifier: "showMapSegue", sender: self)
}
// MARK: Prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showVCB" {
// This gets a reference to the screen that we're about to transition to and from
let toViewController = segue.destination as UIViewController
// Instead of using the default transition animation, we'll ask the segue to use our custom SlideRightTransitionAnimator object to manage the transition animation
toViewController.transitioningDelegate = slideRightTransition
// Add the Interactive gesture transition to the VC
customInteractiveTransition.attachToViewController(viewController: toViewController)
}
}
Thank you in advance!!!
In your viewDidLoad of VC A, you'll want to replace that UISwipeGestureRecognizer with a UIPanGestureRecognizer. Then implement the appropriate .changed state in the gesture handler function.
EDIT: Moreover, to achieve a sliding transition between controllers, I highly recommend using a UIPageViewController instead. That or maybe even a custom UICollectionView solution.
in function handlePanGesture you are taking translation reference from VC A (i.e, gestureRecognizer.view!.superview!)
but instead of that take referance from UIWindow. (i.e, UIApplication.shared.windows.last)
replace gestureRecognizer.view!.superview! with UIApplication.shared.windows.last
it's worked for me.

Interactive transition when presenting modally

I want to use interactive transitions in my app. I have two view controllers. And when user touches a button in first view controller I am presenting second view controller modally. My custom animation is working well but interactive transition is not working. I added a gesture to left edge of screen and when I pan from left edge second view controller is presenting but not interactive it is working as same as touching to button for presenting.
My class:
class MenuTransitionManager: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning {
private var interactive = false
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 2.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let finalFrameForVC = transitionContext.finalFrameForViewController(toViewController)
let containerView = transitionContext.containerView()
let bounds = UIScreen.mainScreen().bounds
toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height)
containerView!.addSubview(toViewController.view)
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: .CurveLinear, animations: {
fromViewController.view.alpha = 0.5
toViewController.view.frame = finalFrameForVC
}, completion: {
finished in
transitionContext.completeTransition(true)
fromViewController.view.alpha = 1.0
})
}
func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
// if our interactive flag is true, return the transition manager object
// otherwise return nil
return self.interactive ? self : nil
}
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self.interactive ? self : nil
}
private var enterPanGesture: UIScreenEdgePanGestureRecognizer!
var sourceViewController: UIViewController! {
didSet {
self.enterPanGesture = UIScreenEdgePanGestureRecognizer()
self.enterPanGesture.addTarget(self, action:"handleOnstagePan:")
self.enterPanGesture.edges = UIRectEdge.Left
self.sourceViewController.view.addGestureRecognizer(self.enterPanGesture)
}
}
func handleOnstagePan(pan: UIPanGestureRecognizer){
// how much distance have we panned in reference to the parent view?
let translation = pan.translationInView(pan.view!)
// do some math to translate this to a percentage based value
let d = translation.x / CGRectGetWidth(pan.view!.bounds) * 0.5
// now lets deal with different states that the gesture recognizer sends
switch (pan.state) {
case UIGestureRecognizerState.Began:
// set our interactive flag to true
self.interactive = true
// trigger the start of the transition
self.sourceViewController.performSegueWithIdentifier("showAction", sender: self)
break
case UIGestureRecognizerState.Changed:
// update progress of the transition
self.updateInteractiveTransition(d)
break
default: // .Ended, .Cancelled, .Failed ...
// return flag to false and finish the transition
self.interactive = false
self.finishInteractiveTransition()
}
}
}
My first view controller:
override func viewDidLoad() {
super.viewDidLoad()
self.transitionManager.sourceViewController = self
}
var transitionManager = MenuTransitionManager()
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return transitionManager
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showAction" {
let toViewController = segue.destinationViewController as UIViewController
toViewController.transitioningDelegate = self
toViewController.modalPresentationStyle = .Custom
}
}
How can I fix it?
You also need to implement interactionControllerForPresentation(_:) on the view controller and vend an instance of UIViewControllerInteractiveTransitioning (which is a sub-protocol of UIPercentDrivenInteractiveTransition that your MenuTransitionManager class already implements).
The documentation around this subject is actually pretty good.

Interactive Dismiss ViewController

I have a storyboard like that :
Article View is presented from segue and animation :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "showArticleFromArticles" {
let ViewToShow = segue.destinationViewController as! ArticleView
ViewToShow.articleToShow = ArticleToShow2
ViewToShow.transitioningDelegate = self
}
}
My animation :
class TransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerInteractiveTransitioning, UIViewControllerTransitioningDelegate, UIViewControllerContextTransitioning {
weak var transitionContext: UIViewControllerContextTransitioning?
var sourceViewController: UIViewController! {
didSet {
print("set")
print(sourceViewController)
enterPanGesture = UIScreenEdgePanGestureRecognizer()
enterPanGesture.addTarget(self, action:"panned:")
enterPanGesture.edges = UIRectEdge.Left
let newSource = sourceViewController as! ArticleView
newSource.WebView.addGestureRecognizer(enterPanGesture)
}
}
let duration = 1.0
var presenting = true
var originFrame = CGRectNull
private var didStartedTransition = false
private var animated = false
private var interactive = false
private var AnimationStyle = UIModalPresentationStyle(rawValue: 1)
private var didFinishedTransition = false
private var percentTransition: CGFloat = 0.0
private var enterPanGesture: UIScreenEdgePanGestureRecognizer!
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
// set up from 2D transforms that we'll use in the animation
let offScreenRight = CGAffineTransformMakeTranslation(container!.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(container!.frame.width, 0)
// start the toView to the right of the screen
toView.transform = offScreenRight
// add the both views to our view controller
container!.addSubview(toView)
container!.addSubview(fromView)
// get the duration of the animation
// DON'T just type '0.5s' -- the reason why won't make sense until the next post
// but for now it's important to just follow this approach
let duration = self.transitionDuration(transitionContext)
// perform the animation!
// for this example, just slid both fromView and toView to the left at the same time
// meaning fromView is pushed off the screen and toView slides into view
// we also use the block animation usingSpringWithDamping for a little bounce
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {
fromView.transform = offScreenLeft
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return duration
}
func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning) {
interactive = true
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
// set up from 2D transforms that we'll use in the animation
let offScreenRight = CGAffineTransformMakeTranslation(container!.frame.width, 0)
let offScreenLeft = CGAffineTransformMakeTranslation(container!.frame.width, 0)
// start the toView to the right of the screen
toView.transform = offScreenRight
// add the both views to our view controller
container!.addSubview(toView)
container!.addSubview(fromView)
// get the duration of the animation
// DON'T just type '0.5s' -- the reason why won't make sense until the next post
// but for now it's important to just follow this approach
let duration = self.transitionDuration(transitionContext)
// perform the animation!
// for this example, just slid both fromView and toView to the left at the same time
// meaning fromView is pushed off the screen and toView slides into view
// we also use the block animation usingSpringWithDamping for a little bounce
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {
fromView.transform = offScreenLeft
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
func containerView() -> UIView? {
return sourceViewController?.view
}
func viewControllerForKey(key: String) -> UIViewController? {
return sourceViewController?.storyboard!.instantiateViewControllerWithIdentifier(key)
}
func viewForKey(key: String) -> UIView? {
return sourceViewController?.storyboard!.instantiateViewControllerWithIdentifier(key).view
}
func initialFrameForViewController(vc: UIViewController) -> CGRect {
return vc.view.frame
}
func finalFrameForViewController(vc: UIViewController) -> CGRect {
return vc.view.frame
}
func isAnimated() -> Bool {
return animated
}
func isInteractive() -> Bool {
return interactive
}
func presentationStyle() -> UIModalPresentationStyle {
return AnimationStyle!
}
func completeTransition(didComplete: Bool) {
didFinishedTransition = didComplete
}
func updateInteractiveTransition(percentComplete: CGFloat) {
percentTransition = percentComplete
}
func finishInteractiveTransition() {
completeTransition(true)
}
func cancelInteractiveTransition() {
completeTransition(true)
}
func transitionWasCancelled() -> Bool {
return didFinishedTransition
}
func targetTransform() -> CGAffineTransform {
return CGAffineTransform()
}
func panned(pan: UIPanGestureRecognizer) {
//print(pan.translationInView(sourceViewController!.view))
switch pan.state {
case .Began:
animated = true
didStartedTransition = true
didFinishedTransition = false
sourceViewController?.dismissViewControllerAnimated(true, completion: nil)
if transitionContext != nil {
startInteractiveTransition(transitionContext!)
}
break
case .Changed:
percentTransition = CGFloat(pan.translationInView(sourceViewController!.view).x / sourceViewController!.view.frame.width)
print(percentTransition)
updateInteractiveTransition(percentTransition)
break
case .Ended, .Failed, .Cancelled:
animated = false
didStartedTransition = false
didFinishedTransition = true
finishInteractiveTransition()
break
case .Possible:
break
}
}
}
From Article View, I call dismiss view like that :
#IBAction func Quit(sender: UIBarButtonItem) {
self.dismissViewControllerAnimated(true, completion: nil)
}
and :
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.presenting = false
return transition
}
And i add the PanGesture like that :
let transition = TransitionManager()
self.transition.sourceViewController = self
But Pan Gesture just dismiss the view, and Interactive is not available
Because i call :
self.dismissViewControllerAnimated(true, completion: nil)
during UIPanGestureRecognizer.began
How can I do this ?
I am using Xcode 7, Swift 2, iOS 9
Thanks !
I found the solution :
i should just use
startInteractiveTransition
to instantiate some things
and use :
func updateInteractiveTransition(percentComplete: CGFloat) {
if self.reverse {
print(percentComplete)
self.tovc.view.frame.origin.x = (self.fromvc.view.frame.maxX * (percentComplete)) - self.fromvc.view.frame.maxX
}
}
to custom my transition.
Easy to use, Just inherent Your UIViewController with InteractiveViewController and you are done
InteractiveViewController
call method showInteractive() from your controller to show as Interactive.

Resources