Skip to content

Instantly share code, notes, and snippets.

@xjones
Created November 26, 2011 03:48
Show Gist options
  • Save xjones/1394947 to your computer and use it in GitHub Desktop.
Save xjones/1394947 to your computer and use it in GitHub Desktop.
TransitionController for animating iOS view controller transitions w/o a controller stack
//
// TransitionController.h
//
// Created by XJones on 11/25/11.
//
#import <UIKit/UIKit.h>
@interface TransitionController : UIViewController
@property (nonatomic, strong) UIView * containerView;
@property (nonatomic, strong) UIViewController * viewController;
- (id)initWithViewController:(UIViewController *)viewController;
- (void)transitionToViewController:(UIViewController *)viewController
withOptions:(UIViewAnimationOptions)options;
@end
//
// TransitionController.m
//
// Created by XJones on 11/25/11.
//
#import "TransitionController.h"
@implementation TransitionController
@synthesize containerView = _containerView,
viewController = _viewController;
- (id)initWithViewController:(UIViewController *)viewController
{
if (self = [super init]) {
_viewController = viewController;
}
return self;
}
- (void)loadView
{
self.wantsFullScreenLayout = YES;
UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
self.view = view;
_containerView = [[UIView alloc] initWithFrame:view.bounds];
_containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_containerView];
[_containerView addSubview:self.viewController.view];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return [self.viewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self.viewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self.viewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
- (void)transitionToViewController:(UIViewController *)aViewController
withOptions:(UIViewAnimationOptions)options
{
aViewController.view.frame = self.containerView.bounds;
[UIView transitionWithView:self.containerView
duration:0.65f
options:options
animations:^{
[self.viewController.view removeFromSuperview];
[self.containerView addSubview:aViewController.view];
}
completion:^(BOOL finished){
self.viewController = aViewController;
}];
}
@end
TransitionController
A UIViewController subclass that allows for easy transitions to new view controllers in any orientation with a specified transition options.
instance methods
- (void)initWithViewController:(UIViewController *)viewController
Initializes a new TransitionController displaying the specified viewController.
- (void)transitionToViewController:(UIViewController *)viewController withOptions:(UIViewAnimationOptions)options
Transitions to a new view controller with the specified options.
@RetVal
Copy link

RetVal commented Jul 23, 2016

Great!

@ilsht
Copy link

ilsht commented Feb 13, 2019

Swift 4.2 implementation.

import Foundation
import UIKit

/*
 * Since swapping windows and/or root view controllers is usually badly animated, it is recommended to set a container view controller as a root view controller, and put/swap controllers (together with transitions) - https://github.com/malcommac/UIWindowTransitions/issues/7, you can see an example of a bug here - https://stackoverflow.com/a/27153956
 * Also it's easier to manage rotations with root transition controller, and other custom animations on child controllers (example of transition controller is here https://gist.github.com/xjones/1394947)
 * Another approach is just to present over current views and do not bother about swapping root view controllers (observers must be unregistered properly then)
 * And for a question on how to preserve child view's orientation while root view must rotate - https://stackoverflow.com/a/38979427
 */

final class ContainerViewController: UIViewController {
    fileprivate var containerView: UIView?
    fileprivate var __shouldAutorotate = true
    fileprivate var __supportedInterfaceOrientations: UIInterfaceOrientationMask = [.portrait]
    
    var currentViewController: UIViewController?
    
    convenience init(with viewController: UIViewController,
            shouldAutorotate: Bool,
            supportedInterfaceOrientations: UIInterfaceOrientationMask) {
        self.init()
        __shouldAutorotate = shouldAutorotate
        __supportedInterfaceOrientations = supportedInterfaceOrientations
        currentViewController = viewController
    }
    
    // note that the window’s root view controller does not react to edgesForExtendedLayout property
    override func loadView() {
        let __view = UIView(frame: UIScreen.main.bounds)
        __view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view = __view
        
        if let currentController = currentViewController {
            self.add(child: currentController, into: __view)
        }
    }
    
    // the topmost view controller always decides if we should and how we should autorotate
    
    override var shouldAutorotate: Bool {
        get {
            return __shouldAutorotate
        }
    }
    
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        get {
            return __supportedInterfaceOrientations
        }
    }
    
    // orientation callbacks will be propagated automatically if child view controller's view is visible

    /*
     * This will invoke loadView() for view controller passed if it wasn't already invoked.
     */
    func transition(to viewController: UIViewController, with options: UIView.AnimationOptions? = nil) {
        viewController.view.frame = self.view.frame
        
        if let animationOptions = options {
            UIView.transition(with: self.view, duration: 0.3, options: animationOptions, animations: { [unowned self] in
                if let currentController = self.currentViewController {
                    self.remove(child: currentController)
                    self.add(child: viewController, into: self.view)
                    self.currentViewController = nil
                }
            }) { [unowned self] finished in
                // avoid overriding child view controller if optional unwrap failed in previous closure
                if (finished && nil == self.currentViewController) {
                    self.currentViewController = viewController
                }
            }
        } else {
            if let currentController = self.currentViewController {
                remove(child: currentController)
                add(child: viewController, into: self.view)
                currentViewController = viewController
            }
        }
    }
    
    // implement your custom transition animations here
}

extension UIViewController {
    func add(child controller: UIViewController, into aView: UIView) {
        self.addChild(controller)
        aView.addSubview(controller.view)
        controller.didMove(toParent: self)
    }

    func remove(child controller: UIViewController) {
        controller.willMove(toParent: nil)
        controller.view.removeFromSuperview()
        controller.removeFromParent()
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment