TabBarController with interactive animated transitioning - ios

I'm trying to implement interactive animated transition between tabs in a TabBar. I'm using a pan gesture recogniser. I made a custom animation and use UIPercentDrivenInteractiveTransition to make the switching interactive. But it seems that I don't really understand the process that goes behind all the animation.
I did manage to make a non-interactive animation, but adding interactivity was somehow difficult. I read many tutorials on the internet and I fully understand how the code snippets that everybody posts works but I somehow can't implement it in my case. I made a TabBar application with 2 tabs both with navigation bar.
Here is my code:
TabBarController.swift
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
var usingGesture = false
var interactiveTransition:UIPercentDrivenInteractiveTransition?
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: "didPan:")
self.view.addGestureRecognizer(panGesture)
self.delegate = self
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tabBarController(tabBarController: UITabBarController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return self.interactiveTransition
}
func didPan(gesture: UIPanGestureRecognizer){
let point = gesture.locationInView(gesture.view)
let percent = fmax(fmin((point.x / 300.0), 0.99), 0.0)
self.interactiveTransition = UIPercentDrivenInteractiveTransition()
switch (gesture.state){
case .Began:
self.usingGesture = true
self.selectedIndex++
case .Changed:
self.interactiveTransition?.updateInteractiveTransition(percent)
case .Ended, .Cancelled:
if percent > 0.5 {
self.interactiveTransition?.finishInteractiveTransition()
} else {
self.interactiveTransition?.cancelInteractiveTransition()
}
self.usingGesture = false
default:
NSLog("Unhandled state")
}
}
}
TransitionToLeft.swift
import UIKit
class TransitionToLeft: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning {
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromView:UIView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView:UIView = transitionContext.viewForKey(UITransitionContextToViewKey)!
transitionContext.containerView().addSubview(fromView)
transitionContext.containerView().addSubview(toView)
toView.frame = CGRectMake(toView.frame.width, 0, toView.frame.width, toView.frame.height)
let fromNewFrame = CGRectMake(-1 * fromView.frame.width, 0, fromView.frame.width, fromView.frame.height)
UIView.animateWithDuration( 0.7, animations:{ () -> Void in
toView.frame = fromView.frame
fromView.frame = fromNewFrame
},{ (Bool) -> Void in
// update internal view - must always be called
transitionContext.completeTransition(true)
})
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.7
}
}
Can someone help me figure this out?

I think your problem is in gesture recognizer. Try to add it to self.selectedViewController.view instead of self.view.
Also, your code is wrong: self.interactiveTransition = UIPercentDrivenInteractiveTransition() should be self.interactiveTransition = TransitionToLeft() I think.

Related

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.

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.

Custom View Transition without use of Storyboard segues (Swift)

I have two views I would like to make a swipe style transition accross and I have done that when there is a segue to act on but the I don't have one here so am not sure how to apply my animation class. All I have in my class is:
let stb = UIStoryboard(name: "Walkthrough", bundle: nil)
let walkthrough = stb.instantiateViewControllerWithIdentifier("walk") as! BWWalkthroughViewController
self.presentViewController(walkthrough, animated: true, completion: nil)
I want to apply apply the following custom segue:
class CustomSegue: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
var firstVCView = self.sourceViewController.view as UIView!
var secondVCView = self.destinationViewController.view as UIView!
// Get the screen width and height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Specify the initial position of the destination view.
secondVCView.frame = CGRectMake(0.0, screenHeight, screenWidth, screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(0.2, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, -screenWidth, 0.0)
secondVCView.frame = CGRectOffset(secondVCView.frame, -screenWidth, 0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
animated: false,
completion:nil)
}
}
}
I cannot get it to work any pointers please?
While the first answer should be "use Storyboard Segues", you can solve custom transitions this way:
Generic Approach
Modify your CustomSegue to adopt both UIStoryboardSegue and UIViewControllerAnimatedTransitioning protocols.
Refactor CustomSegue so that the animation can be used by both protocols.
Setup a delegate to the navigation controller, which can be itself, to supply the custom transitions to push & pop
let animationControllerForOperation create and return an instance of CustomSegue, with an identifier of your choice.
Overview
// Adopt both protocols
class CustomSegue: UIStoryboardSegue, UIViewControllerAnimatedTransitioning {
func animate(firstVCView:UIView,
secondVCView:UIView,
containerView:UIView,
transitionContext: UIViewControllerContextTransitioning?) {
// factored transition code goes here
}) { (Finished) -> Void in
if let context = transitionContext {
// UIViewControllerAnimatedTransitioning
} else {
// UIStoryboardSegue
}
}
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
// return timing
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
// Perform animate using transitionContext information
// (UIViewControllerAnimatedTransitioning)
}
override func perform() {
// Perform animate using segue (self) variables
// (UIStoryboardSegue)
}
}
Below is the complete code example. It has been tested. Notice that I also fixed a few animation bugs from the original question, and added a fade-in effect to emphasize the animation.
CustomSegue Class
// Animation for both a Segue and a Transition
class CustomSegue: UIStoryboardSegue, UIViewControllerAnimatedTransitioning {
func animate(firstVCView:UIView,
secondVCView:UIView,
containerView:UIView,
transitionContext: UIViewControllerContextTransitioning?) {
// Get the screen width and height.
let offset = secondVCView.bounds.width
// Specify the initial position of the destination view.
secondVCView.frame = CGRectOffset(secondVCView.frame, offset, 0.0)
firstVCView.superview!.addSubview(secondVCView)
secondVCView.alpha = 0;
// Animate the transition.
UIView.animateWithDuration(self.transitionDuration(transitionContext!),
animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, -offset, 0.0)
secondVCView.frame = CGRectOffset(secondVCView.frame, -offset, 0.0)
secondVCView.alpha = 1; // emphasis
}) { (Finished) -> Void in
if let context = transitionContext {
context.completeTransition(!context.transitionWasCancelled())
} else {
self.sourceViewController.presentViewController(
self.destinationViewController as! UIViewController,
animated: false,
completion:nil)
}
}
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 4 // four seconds
}
// Perform Transition (UIViewControllerAnimatedTransitioning)
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
self.animate(transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view,
secondVCView: transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!.view,
containerView: transitionContext.containerView(),
transitionContext: transitionContext)
}
// Perform Segue (UIStoryboardSegue)
override func perform() {
self.animate(self.sourceViewController.view!!,
secondVCView: self.destinationViewController.view!!,
containerView: self.sourceViewController.view!!.superview!,
transitionContext:nil)
}
}
Host ViewController Class
class ViewController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .Push:
return CustomSegue(identifier: "Abc", source: fromVC, destination: toVC)
default:
return nil
}
}
}

Resources