I have a navigation based application and I want to change the animation of the push and pop animations. How would I do that?
Edit 2018
Ther
How to change the Push and Pop animations in a navigation based app...
Preamble:
Say you are new to iOS development. Confusingly, Apple provide two transitions which can be used easily. These are: "crossfade" and "flip".
But of course, "crossfade" and "flip" are useless. They are never used. Nobody knows why Apple provided those two useless transitions!
So:
Say you want to do an ordinary, common, transition such as "slide". In that case, you have to do a HUGE amount of work!.
That work, is explained in this post.
Just to repeat:
Surprisingly: with iOS, if you want the simplest, most common, everyday transitions (such as an ordinary slide), you do have to all the work of implementing a full custom transition.
Here's how to do it ...
UIViewControllerAnimatedTransitioning
You need a bool of your own like popStyle
. (Is it popping on, or popping off?)
You must include transitionDuration
(trivial) and the main call, animateTransition
In fact you must write two different routines for inside animateTransition
. One for the push, and one for the pop. Probably name them animatePush
and animatePop
. Inside animateTransition
, just branch on popStyle
to the two routines
The example below does a simple move-over/move-off
In your animatePush
and animatePop
routines. You must get the "from view" and the "to view". (How to do that, is shown in the code example.)
and you must addSubview
for the new "to" view.
and you must call completeTransition
at the end of your anime
So ..
class SimpleOver: NSObject, UIViewControllerAnimatedTransitioning {
var popStyle: Bool = false
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.20
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if popStyle {
animatePop(using: transitionContext)
return
}
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.finalFrame(for: tz)
let fOff = f.offsetBy(dx: f.width, dy: 55)
tz.view.frame = fOff
transitionContext.containerView.insertSubview(tz.view, aboveSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
tz.view.frame = f
}, completion: {_ in
transitionContext.completeTransition(true)
})
}
func animatePop(using transitionContext: UIViewControllerContextTransitioning) {
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.initialFrame(for: fz)
let fOffPop = f.offsetBy(dx: f.width, dy: 55)
transitionContext.containerView.insertSubview(tz.view, belowSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fz.view.frame = fOffPop
}, completion: {_ in
transitionContext.completeTransition(true)
})
}
}
And then ...
Note: strangely, you only have to do this in the "first" view controller. (The one which is "underneath".)
With the one that you pop on top, do nothing. Easy.
So your class...
class SomeScreen: UIViewController {
}
becomes...
class FrontScreen: UIViewController,
UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
let simpleOver = SimpleOver()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
func navigationController(
_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
simpleOver.popStyle = (operation == .pop)
return simpleOver
}
}
That's it.
Push and pop exactly as normal, no change. To push ...
let n = UIStoryboard(name: "nextScreenStoryboardName", bundle: nil)
.instantiateViewController(withIdentifier: "nextScreenStoryboardID")
as! NextScreen
navigationController?.pushViewController(n, animated: true)
and to pop it, you can if you like just do that on the next screen:
class NextScreen: TotallyOrdinaryUIViewController {
@IBAction func userClickedBackOrDismissOrSomethingLikeThat() {
navigationController?.popViewController(animated: true)
}
}
Phew.
Scroll to @AlanZeino and @elias answer for more discussion on how to AnimatedTransitioning
in iOS apps these days!