Swift - How to do a custom slide animation? - ios

I've been looking for swift code to make simple custom slide transitions between views (just left to right or right to left, without bounce) but I only found code for complicated animations. Thanks everyone for your help !
Oscar

I finally found the answer here : http://mathewsanders.com/animated-transitions-in-swift/#custom-transition-animations and adpated it a little bit.
1) Create this Swift NSObject file
class TransitionManager2: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
private var presenting = true
// MARK: UIViewControllerAnimatedTransitioning protocol methods
// animate a change from one viewcontroller to another
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)
// prepare the toView for the animation
toView.transform = self.presenting ? offScreenRight : offScreenLeft
// set the anchor point so that rotations happen from the top-left corner
toView.layer.anchorPoint = CGPoint(x:0, y:0)
fromView.layer.anchorPoint = CGPoint(x:0, y:0)
// updating the anchor point also moves the position to we have to move the center position to the top-left to compensate
toView.layer.position = CGPoint(x:0, y:0)
fromView.layer.position = CGPoint(x:0, y:0)
// 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: 1, initialSpringVelocity: 1, options: nil, animations: {
// slide fromView off either the left or right edge of the screen
// depending if we're presenting or dismissing this view
fromView.transform = self.presenting ? offScreenLeft : offScreenRight
toView.transform = CGAffineTransformIdentity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
// return how many seconds the transiton animation will take
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.4
}
// MARK: UIViewControllerTransitioningDelegate protocol methods
// return the animataor when presenting a viewcontroller
// remmeber that an animator (or animation controller) is any object that aheres to the UIViewControllerAnimatedTransitioning protocol
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// these methods are the perfect place to set our `presenting` flag to either true or false - voila!
self.presenting = true
return self
}
// return the animator used when dismissing from a viewcontroller
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = false
return self
}
}
2) Change the segue between the 2 ViewControllers to "Custom"
3) Add in the first ViewController this code :
let transitionManager = TransitionManager2()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// this gets a reference to the screen that we're about to transition to
let toViewController = segue.destinationViewController as! UIViewController
// instead of using the default transition animation, we'll ask
// the segue to use our custom TransitionManager object to manage the transition animation
toViewController.transitioningDelegate = self.transitionManager
}

What you need to do is subclass UIStoryboardSegue Class and override the perform method.
The code inside your perform method would be something like this
var ourOriginViewController = self.sourceViewController as! UIViewController
ourOriginViewController.navigationController?.pushViewController(self.destinationViewController as! UIViewController, animated: false)
var transitionView = ourOriginViewController.navigationController?.view
UIView.transitionWithView(transitionView!, duration: 1, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: { () -> Void in
}) { (success) -> Void in
}
Assign this segue class to your custom segue in storyboard
Attaching screenshot for reference

Related

SWIFT: touchesEnded not called in my tableViewController

So I have a tableviewController called SettingsViewController, and it has the following touchesEnded function:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
print("yoyoyoyoyoyoyoyQVEWEVIWNE")
let touchLocation = touch.location(in: view)
// 290 because the width of the view is 414, and the SettingsViewController width gets set to 0.7 * the view width in SlideInTransition. 0.7 * 414 is 289.8
if touchLocation.x > 200 {
dismiss(animated: true)
}
}
}
I made the print statement to see if it was being called, which it is not. This view controller is presented with a 'menu-esque' slide in custom transition. I have a suspicion that the bounds of the UIView is the problem somehow. Here's the custom transition code:
class SlideInTransition: NSObject, UIViewControllerAnimatedTransitioning {
var isPresenting: Bool = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Make sure they exist
// The view controller being transitioned from, using the context (ex: here it's the MapViewController)
guard let fromViewController = transitionContext.viewController(forKey: .from),
// The view controller being transitioned to, using the context (ex: here it's the SettingsTableViewController)
let toViewController = transitionContext.viewController(forKey: .to) else {return}
let containerView = transitionContext.containerView
// Constants for appearance of SettingsViewController
let vcWidth = toViewController.view.bounds.width * 0.7
let vcHeight = toViewController.view.bounds.height
if isPresenting {
// Add SettingsViewController to container
containerView.addSubview(toViewController.view)
// Initial frame for view controller, off the screen to the left to start, that way it appears to slide in
toViewController.view.frame = CGRect(x: -vcWidth, y: 0, width: vcWidth, height: vcHeight)
}
// Animate view controller onto the screen, sliding in from left
let transform = {
toViewController.view.transform = CGAffineTransform(translationX: vcWidth, y: 0)
}
// Animate back off screen
let identity = {
// .identity returns the vc to the initial frame, as created above in the isPresenting if statement
fromViewController.view.transform = .identity
}
// Animation of the transition
let duration = transitionDuration(using: transitionContext)
let isCancelled = transitionContext.transitionWasCancelled
UIView.animate(withDuration: duration, animations: {
// If presenting, transform SettingsViewController (to) onto screen, otherwise set it back off the screen.
self.isPresenting ? transform() : identity()
}) { (Bool) in
transitionContext.completeTransition(!isCancelled)
}
}
}
I made my touchesEnded code so that when the user touches outside the viewController, it dismisses, (the view controller only 70% the width of the screen) but it simply doesn't get called, regardless of where on the screen I tap. Any idea why? Thanks.
https://developer.apple.com/documentation/uikit/uiresponder/1621084-touchesended
«If you override this method without calling super (a common use pattern), you must also override the other methods for handling touch events, even if your implementations do nothing.»
This would be a start.

Zoom animation with a UICollectionView inside a UINavigationController

I'm trying to animate the transition from a UICollectionViewController to a UIViewController.
TLDR: in the last function, how can I get the frame that will take the navigation bar into account?
Each cell has a UIImageView, and I want to zoom in on it as I transition to the view controller. This is similar to the Photos app, except that the image is not centered and there are other views around (labels, etc.).
The UIImageView is placed using AutoLayout, centered horizontally and 89pts from the top safe layout guide.
It almost works, except that I can't manage to get the proper frame for the target UIImageView, it doesn't take into account the navigation and so the frame y origin is off by as much.
My UICollectionViewController is the UINavigationControllerDelegate
class CollectionViewController: UINavigationControllerDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == detailSegue,
let destination = segue.destination as? CustomViewController,
let indexPath = sender as? IndexPath else { return }
navigationController?.delegate = self
…
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
return ZoomInAnimator()
default:
return nil
}
}
}
The ZoomInAnimator object looks like that:
open class ZoomInAnimator: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: .from),
let fromContent = (fromViewController as? ZoomAnimatorDelegate)?.animatedContent(),
let toViewController = transitionContext.viewController(forKey: .to),
let toContent = (toViewController as? ZoomAnimatorDelegate)?.animatedContent() else { return }
let finalFrame = toContent.pictureFrame!
// Set pre-animation state
toViewController.view.alpha = 0
toContent.picture.isHidden = true
// Add a new UIImageView where the "from" picture currently is
let transitionImageView = UIImageView(frame: fromContent.pictureFrame)
transitionImageView.image = fromContent.picture.image
transitionImageView.contentMode = fromContent.picture.contentMode
transitionImageView.clipsToBounds = fromContent.picture.clipsToBounds
transitionContext.containerView.addSubview(toViewController.view)
transitionContext.containerView.addSubview(transitionImageView)
// Animate the transition
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0,
options: [.transitionCrossDissolve],
animations: {
transitionImageView.frame = finalFrame
toViewController.view.alpha = 1
}) { completed in
transitionImageView.removeFromSuperview()
toContent.picture.isHidden = false
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
As you can see, I ask both controllers for their UIImageViews and their frames using animatedContent(). This function is defined in both the UICollectionViewController and the UIViewController, which both conforms to this protocol:
protocol ZoomAnimatorDelegate {
func animatedContent() -> AnimatedContent?
}
With AnimatedContent being a simple struct:
struct AnimatedContent {
let picture: UIImageView!
let pictureFrame: CGRect!
}
Here's how that function is implemented on both sides.
This side works fine (the UICollectionViewController / fromViewController in the transition):
extension CollectionViewController: ZoomAnimatorDelegate {
func animatedContent() -> AnimatedContent? {
guard let indexPath = collectionView.indexPathsForSelectedItems?.first else { return nil }
let cell = cellOrPlaceholder(for: indexPath) // Custom method to get the cell, I'll skip the details
let frame = cell.convert(cell.picture.frame, to: view)
return AnimatedContent(picture: cell.picture, pictureFrame: frame)
}
}
This is the problematic part.
The picture frame origin is 89pts and ignores the navigation bar which adds a 140pt offset. I tried all sorts of convert(to:) without any success. Also, I'm a bit concerned about calling view.layoutIfNeeded here, is it the way to do it?
class ViewController: ZoomAnimatorDelegate {
#IBOutlet weak var picture: UIImageView!
func animatedContent() -> AnimatedContent? {
view.layoutIfNeeded()
return AnimatedContent(picture: picture, profilePictureFrame: picture.frame)
}
}
EDIT: Here's an updated version of animateTransition (not using snapshots yet, but I'll get there)
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: .from) as? CollectionViewController,
let fromContent = fromViewController.animatedContent(),
let toViewController = transitionContext.viewController(forKey: .to) as? ViewController,
let toImageView = toViewController.picture else { return }
transitionContext.containerView.addSubview(toViewController.view)
toViewController.view.frame = transitionContext.finalFrame(for: toViewController)
toViewController.view.setNeedsLayout()
toViewController.view.layoutIfNeeded()
let finalFrame = toViewController.view.convert(toImageView.frame, to: transitionContext.containerView)
let transitionImageView = UIImageView(frame: fromContent.pictureFrame)
transitionImageView.image = fromContent.picture.image
transitionImageView.contentMode = fromContent.picture.contentMode
transitionImageView.clipsToBounds = fromContent.picture.clipsToBounds
transitionContext.containerView.addSubview(transitionImageView)
// Set pre-animation state
toViewController.view.alpha = 0
toImageView.isHidden = true
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0,
options: [.transitionCrossDissolve],
animations: {
transitionImageView.frame = finalFrame
toViewController.view.alpha = 1
}) { completed in
transitionImageView.removeFromSuperview()
toImageView.isHidden = false
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
The problem is that you are obtaining the final frame incorrectly:
let finalFrame = toContent.pictureFrame!
Here's what you should have done:
Start by asking the transition context for the final frame of the to view controller’s view, by calling finalFrame(for:). The fact that you never call that method is a clear bug; you should always call it!
Now use that as either the frame to animate to (if you know that the image will fill it completely), or use it as the basis of the calculation of that frame.
What I do in the latter case is to perform a dummy layout operation to learn what the image frame will be within the view controller’s view, and then convert the coordinate system to that of the transition context. The fact that I don't see you performing any coordinate system conversion is a sign of another bug in your code. Remember, frame is calculated in terms of a view's superview's coordinate system.
Here’s an example from one of my apps; it’s a presented VC not a pushed VC, but that makes no difference to the calculation:
Observe how I know where the red swatch will be in the final frame of the presented view controller's view, and I move the red swatch snapshot to that frame in terms of the transition context's coordinate system.
Also note that I use a snapshot view as a proxy. You are using a loose image view as a proxy and that might be fine too.

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.

PushViewController details?

What I am trying to do is a custom animation of pushing ViewController from the left side.
I have created my custom transitioning delegate and I provide my custom animation, and everything works fine (new view slides from the left side).
The only problem is that push animation in iOS isn't only about sliding a view from the right side. The VC being obscured is also slightly moving in the same directions as the VC being pushed. Also, navigation bar kinda blinks. I can of course try to imitate this behaviour by guessing what the parameters should be (for example how much the VC being obscured moves on different iPhones), but maybe it is possible to find the values somewhere?
Help greatly appreciated.
I would create a UIViewControllerAnimatedTransitioning protocol abiding object
class CustomHorizontalSlideTransition: NSObject, UIViewControllerAnimatedTransitioning {
var operation: UINavigationControllerOperation = .Push
convenience init(operation: UINavigationControllerOperation) {
self.init()
self.operation = operation
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView()
let disappearingVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let appearingVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let bounds = UIScreen.mainScreen().bounds
if self.operation == .Push {
appearingVC.view.frame = CGRectOffset(bounds, -bounds.size.height, 0)
containerView!.addSubview(disappearingVC.view)
containerView!.addSubview(appearingVC.view)
} else {
appearingVC.view.frame = bounds
disappearingVC.view.frame = bounds
containerView!.addSubview(appearingVC.view)
containerView!.addSubview(disappearingVC.view)
}
UIView.animateWithDuration(transitionDuration(transitionContext),
delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: { () -> Void in
if self.operation == .Push {
appearingVC.view.frame = bounds
} else {
disappearingVC.view.frame = CGRectOffset(bounds, -bounds.size.width, 0)
}
}) { (complete) -> Void in
transitionContext.completeTransition(true)
}
}
}
Then in your "From" and "To" view controllers, set the navigationController's delegate to self in view ViewDidAppear
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
navigationController?.delegate = self
}
The in both view controllers, override the following to provide a transitionAnimatedTransition delegate method and return the protocol abiding instance for your animation
override func transitionAnimatedTransition(operation: UINavigationControllerOperation) -> UIViewControllerAnimatedTransitioning? {
return CustomHorizontalSlideTransition(operation: operation)
}

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.

Resources