iOS Programming Recipe 28: View Controller Containment & Transitioning

The view controllers of your application coordinate between your data and what is actually displayed on screen. In a well written MVC application the view controllers will be where much of your essential app logic lives and in turn where you will spend a lot of time. UIKit supplies many built-in view controllers and view controllers of view controllers such as UITabBarController or UINavigationController. With these you can accomplish an amazing amount of tasks, but sometimes you need or want something a bit more custom! This article will discuss a few helpful techniques for developing and working with custom view controllers.

Subclassing UIViewController

UIViewController will be the basis of all your custom view controllers, unless you want to go “full Brichter.” It is exceptionally easy to subclass, and the perfect starting point for some custom work!

UIViewController out of the box is setup to receive callbacks for things like device rotations, view loading, and device memory warnings. All of these conveniences make UIViewController a very powerful class. Subclassing UIViewController doesn’t have any required overrides, so you just subclass and add functionality as required, which is quite nice. In fact this is the exact setup of the Single View Application Template in Xcode. They create a new application for you with a UIViewController subclass called ViewController. If you would like to follow along with this tutorial, go ahead and create a new single view based application in Xcode now, we named ours CustomViewControllers.

1

View Controller Containment

Let’s make up a contrived scenario, say you need ViewController to act as a container of other view controller’s similar to a UITabBarController or a UINavigationController. First, we need to learn how to properly contain another view controller.

Adding A Child View Controller

1
2
3
4
5
UIViewController *viewController = [UIViewController new];

[self addChildViewController:viewController];
[self.view addSubview:viewController.view];
[viewController didMoveToParentViewController:self];
  • First you need to add the view controller as your child.
  • Next you add the view controller’s view as a subview of your view.
  • Finally, you inform the new child view controller that it has moved to a new parent.
Removing A Child From A Parent

1
2
3
[viewController willMoveToParentViewController:nil];
[viewController.view removeFromSuperview];
[viewController removeFromParentViewController];
  • First we need to inform the current child view controller that we intend to orphan it.
  • Then we remove it’s view from our view hierarchy.
  • Finally, we remove it from our children.

View Controller Transitioning

Transitioning between child view controllers can be very handy! UINavigationController has a nice slide animation while transitioning between view controllers, but maybe you would like to define your own cool animation to switch between children.

This can easily be done using the following method (defined on UIViewController) in conjunction with the containment shown above.


1
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;

This method will handle adding you new view controller to the view hierarchy and setting up for a child transition. However, it is left to the caller to add the proper containment. The code below shows how this would be used.


1
2
3
4
5
6
7
8
9
10
11
12
13
UIViewController *nextViewController = [UIViewController new];

// Containment
[self addChildViewController:nextViewController];
[self.currentChildViewController willMoveToParentViewController:nil];

[self transitionFromViewController:self.currentViewController toViewController:nextViewController duration:_animationDuration.value options:0 animations:^{
    // Do any fancy animation you like
} completion:^(BOOL finished) {
    [nextViewController didMoveToParentViewController:self];
    [self.currentChildViewController removeFromParentViewController];
    self.currentChildViewController = nextViewController;
}];
  • This is fairly self explanatory, we are basically just wrapping the transitioning in the containment code explained above.

Lets Build The Sample App

Now that you know the basics of how containment and child view controller transitioning works, let’s use this to build a basic sample app starting with our single view based application we created earlier. Basically, we are going to build a playground type app for working with view controller transitions.

  • Open ViewController.h, from the single view based application and add the following IBOutlets.

1
2
3
4
5
@property (strong, nonatomic) IBOutlet UISegmentedControl *transitionStyle;
@property (strong, nonatomic) IBOutlet UISlider *velocity;
@property (strong, nonatomic) IBOutlet UISlider *alpha;
@property (strong, nonatomic) IBOutlet UISlider *scale;
@property (strong, nonatomic) IBOutlet UISlider *animationDuration;
  • Each of these will allow us to tweak various transitioning properties at runtime.

  • Now switch to ViewController.xib and add the sliders and segmented control we just defined as shown below.

  • The range of all sliders is 0 to 1, except the animation duration slider which has a range of 0.3 to 10 as shown in the UI.

2

  • Now switch to ViewController.m, this is where all the magic happens.
  • First we need to create an enum for the transitioning styles we added to the segmented control in Interface Builder. At the top of ViewController.m add the following enum definition.

1
2
3
4
5
6
7
8
// These values align with the segments in the segmented control names "transitionStyle"
typedef NS_ENUM(NSInteger, ViewControllerTransition) {
    ViewControllerTransitionSlideFromTop   = 0,
    ViewControllerTransitionSlideFromLeft,
    ViewControllerTransitionSlideFromBottom,
    ViewControllerTransitionSlideFromRight,
    ViewControllerTransitionRotateFromRight
};
  • We know that every good demo app has a random color function, we are going to borrow ours from Kyle Fox on Gist. Add the following color() function after the enum definition.

1
2
3
4
5
6
7
8
// https://gist.github.com/kylefox/1689973
UIColor* color()
{
    CGFloat hue = ( arc4random() % 256 / 256.0 );  //  0.0 to 1.0
    CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5;  //  0.5 to 1.0, away from white
    CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5;  //  0.5 to 1.0, away from black
    return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
}
  • Next we need to add a private property to the Interface Extension named currentChildViewController. This is just a convenience property for accessing our current child. Add the following extension.

1
2
3
4
5
@interface ViewController ()

@property (nonatomic, weak) UIViewController *currentChildViewController;

@end
  • Next we will create a method named nextViewController that will create a generic UIViewController for us to use as our child. Of course we need to give it some personality so I have added a fix visual enhancements in here, for which you will need to add an import for QuartzCore to the top of ViewController.m. Here is nextViewController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (UIViewController *)nextViewController
{
    UIViewController *viewController = [UIViewController new];
    viewController.view.frame = CGRectInset(self.view.bounds, 0, 200);
    UILabel *label = [[UILabel alloc] initWithFrame:viewController.view.bounds];
    label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    label.textColor = [UIColor whiteColor];
    label.backgroundColor = [UIColor clearColor];
    label.textAlignment = NSTextAlignmentCenter;
    label.numberOfLines = 0;
    label.text = @"Contained View Controller's View\n\nClick To Transition";
    [viewController.view addSubview:label];

    viewController.view.backgroundColor = color();
    viewController.view.layer.borderWidth = 6;
    viewController.view.layer.cornerRadius = 8;
    viewController.view.layer.borderColor = color().CGColor;
    viewController.view.layer.shadowColor = [UIColor blackColor].CGColor;
    viewController.view.layer.shadowOffset = CGSizeZero;
    viewController.view.layer.shadowOpacity = 0.5;

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
    [viewController.view addGestureRecognizer:tap];

    return viewController;
}
  • We just create a new generic UIViewController, add a label, change a few layer properties, and finally add a UITapGestureRecognizer from which we will trigger transitioning.

  • Next let’s implement tap: since we just assign the selector as an action for the UITapGestureRecognizer. This will just call another method that we have yet to define called transitionToNextViewController


1
2
3
4
5
- (void)tap:(UIGestureRecognizer *)gr
{
    if (gr.state == UIGestureRecognizerStateEnded)
        [self transitionToNextViewController];
}
  • now we need to implement transitionToNextViewController, this is where we will called the transitioning API we covered earlier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)transitionToNextViewController
{
    UIViewController *nextViewController = [self nextViewController];

    // Containment
    [self addChildViewController:nextViewController];
    [self.currentChildViewController willMoveToParentViewController:nil];

    nextViewController.view.transform = [self startingTransformForViewControllerTransition:self.transitionStyle.selectedSegmentIndex];

    [self transitionFromViewController:self.currentChildViewController toViewController:nextViewController duration:_animationDuration.value options:0 animations:^{
        self.currentChildViewController.view.alpha = _alpha.value;
        CGAffineTransform transform = CGAffineTransformMakeTranslation(-nextViewController.view.transform.tx * _velocity.value, -nextViewController.view.transform.ty * _velocity.value);
        transform = CGAffineTransformRotate(transform, acosf(nextViewController.view.transform.a));
        self.currentChildViewController.view.transform = CGAffineTransformScale(transform, _scale.value, _scale.value);

        nextViewController.view.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        [nextViewController didMoveToParentViewController:self];
        [self.currentChildViewController removeFromParentViewController];
        self.currentChildViewController = nextViewController;
    }];
}
  • First this method gets a new UIViewController to transition to from our nextViewController method, then performs containment, and adds a starting transform from a convenience method called startingTransformForViewControllerTransition:, which we will define in a moment.
  • Within the transitioning animation block we use the current value of our sliders to adjust how the animation is performed. Don’t worry too much if don’t understand the math, all we are doing is adjusting transforms.
  • Finally, in the completion block we finish the containment and assign the new child view controller to our convenience property.

  • Now let’s implement startingTransformForViewControllerTransition:, which we used in the method above. This method will return a starting transform for the transition type passed.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (CGAffineTransform)startingTransformForViewControllerTransition:(ViewControllerTransition)transition
{
    CGFloat width = CGRectGetWidth(self.view.bounds);
    CGFloat height = CGRectGetHeight(self.view.bounds);
    CGAffineTransform transform = CGAffineTransformIdentity;

    switch (transition)
    {
        case ViewControllerTransitionSlideFromTop:
            transform = CGAffineTransformMakeTranslation(0, -height);
            break;
        case ViewControllerTransitionSlideFromLeft:
            transform = CGAffineTransformMakeTranslation(-width, 0);
            break;
        case ViewControllerTransitionSlideFromRight:
            transform = CGAffineTransformMakeTranslation(width, 0);
            break;
        case ViewControllerTransitionSlideFromBottom:
            transform = CGAffineTransformMakeTranslation(0, height);
            break;
        case ViewControllerTransitionRotateFromRight:
            transform = CGAffineTransformMakeTranslation(width, 0);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;
        default:
            break;
    }

    return transform;
}
  • Here we use the parent view controller’s view (self) as the bounding box for creating the transforms. This will create the effect of the view controllers moving on and off screen. Each value corresponds to a segment in our segmented control, this way we can change the transition type at runtime.

  • Next we need to add an implementation of viewDidLayoutSubviews:. This is where we will set the shadow path for the shadow we created in nextViewController. If we don’t do this the app will still work, however, performance will suffer drastically.


1
2
3
4
5
6
- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    self.currentChildViewController.view.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.currentChildViewController.view.bounds cornerRadius:8].CGPath;
}
  • Finally, we just need to implement viewDidLoad in order to add an initial child view controller.

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)viewDidLoad
{
    [super viewDidLoad];

    // Add an initial contained viewController
    UIViewController *viewController = [self nextViewController];

    // Contain the view controller
    [self addChildViewController:viewController];
    [self.view addSubview:viewController.view];
    [viewController didMoveToParentViewController:self];
    self.currentChildViewController = viewController;
}

Thats it? Build and run, you should now have a functional transitioning playground app! Go ahead and add more sliders and transitioning styles to test and tweak your creative new transitions!

3

This concludes our crash course on view controller containment and child transitioning. While we covered the basics there are may other small details with respect to these topics. We suggest reading through the UIViewController header and the Apple documentation to get a full treatment!

Please let us know what you think of this tutorial and if you have any questions or concerns, we would love to hear from you!

Comments

  1. Cool. Thanks for the knowledge transfer and screenshots. Do you have a ZIP that you could post so I could see how everything fits together?

    Thanks !

  2. Please can you update this recipe for XCode 5 ?
    I can’t follow this tutorial because ViewController.xib is not automatically created. I think XCode 5 uses storyboard instread.

    Appreciated!

  3. ” import for QuartzCore to the top of ViewController.m”

    You don’t explain this. What should we do exactly?

  4. Don McBrien says:

    Hi mikett,
    Excellent tutorial. I have found the notion of embedding view controllers in view controllers difficult to grasp. Everybody teaches you to use the standard Tab and Navigation controllers that Apple supplies (but who really wants to do that and look like everybody else?). You made it so simple. Thanks.
    It would be nice to know what goes on in didMoveToParentViewController: and willMoveToParentViewController: respectively.
    All the best
    Don

Trackbacks

  1. […] iOS Programming Recipe 28: View Controller Containment & Transitioning: The view controllers of your application coordinate between your data and what is actually displayed on screen. In a well written MVC application the view controllers will be where much of your essential app logic lives and in turn where you will spend a lot of time. – by mikett – http://nscookbook.com/2013/08/ios-programming-recipe-28-view-controller-containment-transitioning/ […]

Speak Your Mind

*

css.php
Privacy Policy