How to use UIViewControllerAnimatedTransitioning with UINavigationController?
@rounak has the right idea, but sometimes it helps to have code ready without having to download from github.
Here are the steps that I took:
Make your FromViewController.m conform to
UINavigationControllerDelegate
. Other sample code out there tells you to conform toUIViewControllerTransitioningDelegate
, but that's only if you're presenting the ToViewController.@interface ViewController : UIViewController
Return your custom transition animator object in the delegate callback method in FromViewController:
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
TransitionAnimator *animator = [TransitionAnimator new];
animator.presenting = (operation == UINavigationControllerOperationPush);
return animator;
}Create your custom animator class and paste these sample methods:
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 0.5f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
// Grab the from and to view controllers from the context
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// Set our ending frame. We'll modify this later if we have to
CGRect endFrame = CGRectMake(80, 280, 160, 100);
if (self.presenting) {
fromViewController.view.userInteractionEnabled = NO;
[transitionContext.containerView addSubview:fromViewController.view];
[transitionContext.containerView addSubview:toViewController.view];
CGRect startFrame = endFrame;
startFrame.origin.x += 320;
toViewController.view.frame = startFrame;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
toViewController.view.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
else {
toViewController.view.userInteractionEnabled = YES;
[transitionContext.containerView addSubview:toViewController.view];
[transitionContext.containerView addSubview:fromViewController.view];
endFrame.origin.x += 320;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
fromViewController.view.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
}
Essentially, the animator is the object doing the heavy lifting. Of course, you can make your UINavigationControllerDelegate be a separate object, but that depends on how your architect your app.
Navigation controller custom transition animation
To do a custom transition with navigation controller (UINavigationController
), you should:
Define your view controller to conform to
UINavigationControllerDelegate
protocol. For example, you can have a private class extension in your view controller's.m
file that specifies conformance to this protocol:@interface ViewController () <UINavigationControllerDelegate>
@endMake sure you actually specify your view controller as your navigation controller's delegate:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.delegate = self;
}Implement
animationControllerForOperation
in your view controller:- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController*)fromVC
toViewController:(UIViewController*)toVC
{
if (operation == UINavigationControllerOperationPush)
return [[PushAnimator alloc] init];
if (operation == UINavigationControllerOperationPop)
return [[PopAnimator alloc] init];
return nil;
}Implement animators for push and pop animations, e.g.:
@interface PushAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@end
@interface PopAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation PushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
@implementation PopAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@endThat does fade transition, but you should feel free to customize the animation as you see fit.
If you want to handle interactive gestures (e.g. something like the native swipe left-to-right to pop), you have to implement an interaction controller:
Define a property for an interaction controller (an object that conforms to
UIViewControllerInteractiveTransitioning
):@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController;
This
UIPercentDrivenInteractiveTransition
is a nice object that does the heavy lifting of updating your custom animation based upon how complete the gesture is.Add a gesture recognizer to your view. Here I'm just implementing the left gesture recognizer to simulate a pop:
UIScreenEdgePanGestureRecognizer *edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFromLeftEdge:)];
edge.edges = UIRectEdgeLeft;
[view addGestureRecognizer:edge];Implement the gesture recognizer handler:
/** Handle swipe from left edge
*
* This is the "action" selector that is called when a left screen edge gesture recognizer starts.
*
* This will instantiate a UIPercentDrivenInteractiveTransition when the gesture starts,
* update it as the gesture is "changed", and will finish and release it when the gesture
* ends.
*
* @param gesture The screen edge pan gesture recognizer.
*/
- (void)handleSwipeFromLeftEdge:(UIScreenEdgePanGestureRecognizer *)gesture {
CGPoint translate = [gesture translationInView:gesture.view];
CGFloat percent = translate.x / gesture.view.bounds.size.width;
if (gesture.state == UIGestureRecognizerStateBegan) {
self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
[self popViewControllerAnimated:TRUE];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
[self.interactionController updateInteractiveTransition:percent];
} else if (gesture.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [gesture velocityInView:gesture.view];
if (percent > 0.5 || velocity.x > 0) {
[self.interactionController finishInteractiveTransition];
} else {
[self.interactionController cancelInteractiveTransition];
}
self.interactionController = nil;
}
}In your navigation controller delegate, you also have to implement
interactionControllerForAnimationController
delegate method- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
return self.interactionController;
}
If you google "UINavigationController custom transition tutorial" and you'll get many hits. Or see WWDC 2013 Custom Transitions video.
How to apply custom transition animation in UINavigationControllerDelegate to specific push and pop events?
You Can Check the fromView and ToView controller in below delegate method for perform action
extension BaseViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
if toVc is RandomViewController44{
return InAnimator()
}
return nil
case .pop:
if RandomViewController27 is fromVC{
return OutAnimator()
}
return nil
default:
return nil
}
}
}
You need to check from and toView controller when this method called as set condition as per your requirement
UINavigationControllerDelegate methods, custom transitioning using navigation controller in ios 8
The UINavigationController maintains a weak reference to its delegate. If there is no strong reference then the UINavigationControllerDelegate object is deallocated once the transition is complete.
You can set the delegate object in Storyboard Editor. Add an object by dragging an object from the palette onto the Navigation Controller scene, between the First Responder and the Exit icons. Set its class to that of your delegate object. Make certain the module is your current application module. Then control-drag from the Navigation Controller icon to the object icon and choose "delegate" from the popup menu.
This seems to maintain a strong reference to the delegate object. The delegate object will then be available to invoke the unwind transition. You don't need to create the object yourself or set the delegate reference.
I found this technique in a blog post by Scott James Remnant.
Scott James Remnant - Custom iOS Segues, Transitions, and Animations - The Right Way
Change transition to ONE ViewController in a UINavigationController while keeping back-swipe
You can try to subclass UINavigationController and manage interactivePopGestureRecognizer on your own.
class CustomNavController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
}
extension CustomNavController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === interactivePopGestureRecognizer {
return viewControllers.count > 1
}
return true
}
}
Using UIViewControllerAnimatedTransitioning Without Storyboard Segues
As you correctly pointed out Segues are only available if you use storyboards, so the prepare for segue will not work with xibs
You have created and set the UINavigationControllerDelegate
and now just want to start the animation to the other view controller.
Therefore you just need to call push on your navigation view controller:
let vc = DetailViewController(nibName: "DetailViewController", bundle: nil)
self.navigationController.pushViewController(vc, animated: true)
This code creates and presents an instance of DetailViewController
(rename to the correct name you use). If you set the delegate for the navigation view controller correctly, your custom animation will be applied.
Custom UIViewController animations WITHOUT A UINAVIGATIONCONTROLLER
You "push" a view controller onto a navigation stack. But when you present a view controller without navigation controller, you name it "present" (not "push"). Means the term "pushing" a view controller is hardly connected to UINavigationController
.
So you could just google for something like "UIViewController custom transition" or anything similar.
I did great success following a this great article by Teehan+Lax. Just read it, it should lead you the right way.
EDIT Okay. I gonna add some info to provide a better answer for you.
First of all, you'll create a new subclass of NSObject
and conform to the UIViewControllerAnimatedTransitioning
protocol:
@interface JWTransitionAnimator <UIViewControllerAnimatedTransitioning>
+ (JWTransitionAnimator *)transitionAnimatorForPresentation;
+ (JWTransitionAnimator *)transitionAnimatorForDismissal;
@end
Then you let the magic happen in your implementation:
@interface JWTransitionAnimator ()
@property (atomic, assign, getter=isPresenting) BOOL presenting;
@end
@implementation JWTransitionAnimator
+ (JWTransitionAnimator *)transitionAnimatorForPresentation {
JWTransitionAnimator *animator = [[self alloc] init];
[animator setPresenting:YES];
return animator;
}
+ (JWTransitionAnimator *)transitionAnimatorForDismissal {
return [[self alloc] init];
}
// implement the protocol:
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitioningContext {
return 0.3; // or any other duration.
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitioningContext {
UIViewController *fromViewController = [transitioningContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext UITransitionContextToViewController];
UIView *fromView = [fromViewController view];
UIView *toView = [toViewController view];
if ([self isPresenting]) {
[fromView setUserInteractionEnabled:NO];
[[transitionContext containerView] addSubview:fromView];
[[transitionContext containerView] addSubview:toView];
[toView setAlpha:0.0];
[toView setTransform:CGAffineTransformScale([fromView transform], 0.3, 0.3)];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
[fromView setTintAdjustmentMode:UIViewTintAdjustmentModeDimmed];
[toView setAlpha:1.0];
[toView setTransform:CGAffineTransformScale([toView transform], 1.0, 1.0)];
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}
else {
// reverse your animation here (change "fromView" for "toView" and vice-versa)
}
}
Your presenting viewController must conform to the UIViewControllerTransitionDelegate
protocol. Implement it as follows:
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return [JWTransitionAnimator transitionAnimatorForPresentation];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return [JWTransitionAnimator transitionAnimatorForDismissal];
}
The you present the view controller like this:
- (IBAction)presentViewController:(id)sender {
UIViewController *yourVC = [[UIViewController alloc] init]; // however you create it
[yourVC setModalPresentationStyle:UIModalPresentationStyleCustom];
[yourVC setTransitionDelegate:self];
[self presentViewController:yourVC animated:YES completion:NULL];
}
Related Topics
How to Implement Localization in Swift Ui
Accessing the Settings App from Your App in iOS 8
Wait for Swift Animation to Complete Before Executing Code
How to Activate Tcp Keepalive on Apple iOS Devices
Encryption with Rsa Public Key on iOS
How to Disable User Interaction on Mkmapview
To Change the Color of Unselected Uitabbar Icon in iOS 7
Completely Disable Firebase/Analytics to Stop Console Spam on App Startup
Better Way to Get the User's Name from Device
iPhone - Didselectrowatindexpath: Only Being Called After Long Press on Custom Cell
Swift: Save Video from Nsurl to User Camera Roll
Google Analytics 3.08 iOS Idfa Class Missing, Won't Collect Idfa
How to Upload Video to Server from Iphone
How Big Can the Payload Be When Sending Data via Watchconnectivity
#Import <Libxml/Tree.H> File Not Found After Xcode Update