Think And Build

iOS

Working with custom Container View Controllers

Posted on .

Working with custom Container View Controllers

Introduction

Hey Devs!
It has been a while since my last article. A lot of things happened in the meanwhile:
I changed my nickname (Bitwaker, I love it!) and Gradient 2 beta is a few days to completion (check back soon here!).

The time has come to write again, though, so I can stop here with the emo-intro-babbling and go straight to the focus of this new post.

In the last few months I had the chance to speak to many developers involved in a variety of projects. What came out of these conversations is that even the most skilled developer sometimes has doubts about how to organise a proper ViewController hierarchy. Crafting a good navigation pattern is an incredibly important part of the project and if you don’t watch your step carefully, you will easily fall into a labyrinth of intricate, uncontrolled and coupled connections. A nightmare.
You can obviously choose a standard solution like a UINavigation Controller or a UITabBarController, but sometimes they are simply not enough.

With iOS5 Apple added a great feature not many developers seem to be aware of: the ability to build custom viewController containers, with which you can build a better and more proper navigation, just following a few rules proposed by Apple.

Let’s dive into the topic building a simple application.
Here a short video to show you what we are going to build:

Controller’s factory

In this fantastically useless application we are going to build a navigation through infinite controllers that will be updated tapping a button.

Here’s a simple scheme to understand how it works.

Container: This is a subclass of a UIViewcontroller, it has a specific subview used as container for other controllers’ views called detailView and a button to present a new Detail Controller. A reference of the current Detail Controller is stored in the currentDetailController property.
This is our Custom Container Controller and its main responsibility is to define how to present their children to the user.

Detail: This is a simple UIViewController which contains a Label and a UIGestureRecognizer. Swiping over its view the Label’s color randomly changes.
This is the class whose instances will be children of the Container Controller. The view of this controller will be attached to the container’s detailView but the Detail Controller still manages it, receiving gesture events.

You can find the project source at the end of the article, download it to follow the next steps.

Adding controller’s children

Before moving on, a big hi-five to Jack Flintermann, the creator of FlatUI Kit, a really cool library which “flattens” the standard UIKit components (https://github.com/Grouper/FlatUIKit). I’ve used some of its classes to style this example.

Open the file ContainerViewController and take a look at the function presentDetailController.
The Container controller puts a new Detail controller in its hierarchy. We can follow the code row by row.

[code lang=”Obj-C”]
– (void)presentDetailController:(UIViewController*)detailVC{

//0. Remove the current Detail View Controller showed
if(self.currentDetailViewController){
[self removeCurrentDetailViewController];
}

//1. Add the detail controller as child of the container
[self addChildViewController:detailVC];

//2. Define the detail controller’s view size
detailVC.view.frame = [self frameForDetailController];

//3. Add the Detail controller’s view to the Container’s detail view and save a reference to the detail View Controller
[self.detailView addSubview:detailVC.view];
self.currentDetailViewController = detailVC;

//4. Complete the add flow calling the function didMoveToParentViewController
[detailVC didMoveToParentViewController:self];

}

Step 0
The step commented with 0 will be explained later, but essentially we remove the current displayed Detail Controller from the hierarchy.

Step 1
The function addChildViewController is part of the new set of functions added to iOS for custom containers.
As simple as it is, we are adding the new Detail Controller as child of the Container Controller.

Step 2
The new Detail Controller’s view is going to be attached to a predefined view of the Container. So we can check its frame and adapt it as needed.

Step 3
We can now attach the Detail’s view to the Container detailView and store the new Detail controller as the current Detail Controller.

Step 4
The function didMoveToParentViewController is again a new addition of the UIViewController class. Thanks to this function we send a message to the Detail Controller object to confirm that it is now child of another controller.

Removing controller’s children

Now, in the same file, check the function – (void)removeCurrentDetailViewController.

Step 1
We send the message willMoveToParentViewController with the parameter **nil** to the current Detail Controller, informing that it is going to be removed by its father in the hierarchy. This message is part of the new set of iOS functions.

Step 2
The current Detail controller’s view can be removed by its superview.

Step 3
Calling the standard function removeFromParentViewController we remove the current Detail Controller from the Container hierarchy.
When this function is called, the function didMoveToParentViewController is automatically called with nil as parameter on the Detail Controller.

The result!

The first Detail Controller is created into the viewDidLoad function of the Container Controller. The method initWithString:withColor helps us casting an instance of DetailViewController and thanks to the presentDetailController function we add it as the current Detail Controller.

As previously noted, you decide when it’s time to present a new Detail Controller tapping a button stored on the Container Controller main view. This button is connected to the function addDetailController and its code is extremely simple:


- (IBAction)addDetailController:(id)sender {
    DetailViewController *detailVC = [[DetailViewController alloc]initWithString:@"This is a new viewController!" withColor:[UIColor asbestosColor]];
    
    /* Mode 1 */
    [self presentDetailController:detailVC];
    
    /* Mode 2 */
    //[self swapCurrentControllerWith:detailVC];
}

Essentially it creates a new Detail View Controller and it calls the function presentDetailController.

You can compile and run, push the button “New Controller” to create a new Detail Controller and swipe over the view to change the label color.

Add transitions

Let’s say that the switch of controllers is totally unnoticed. It would then be a good idea to create a sort of animation to clarify that a new controller has been pushed.

Instead of using 2 separate methods to add and remove the Detail Controllers we merge all the operations in a single function that we call swapCurrentControllerWith: and that takes the new ViewController to be pushed as input .
Here’s the full code commented code:


- (void)swapCurrentControllerWith:(UIViewController*)viewController{
    
    //1. The current controller is going to be removed
    [self.currentDetailViewController willMoveToParentViewController:nil];
    
    //2. The new controller is a new child of the container
    [self addChildViewController:viewController];
    
    //3. Setup the new controller's frame depending on the animation you want to obtain
    viewController.view.frame = CGRectMake(0, 2000, viewController.view.frame.size.width, viewController.view.frame.size.height);

    //The transition automatically removes the old view from the superview and attaches the new controller's view as child of the
    //container controller's view
    
    //Save the button position... we'll need it later
    CGPoint buttonCenter = self.button.center;
    
    [self transitionFromViewController:self.currentDetailViewController toViewController:viewController
                              duration:1.3 options:0
                            animations:^{
                                
                                //The new controller's view is going to take the position of the current controller's view
                                viewController.view.frame = self.currentDetailViewController.view.frame;
                                
                                //The current controller's view will be moved outside the window
                                self.currentDetailViewController.view.frame = CGRectMake(0,
                                                                                         -2000,
                                                                                         self.currentDetailViewController.view.frame.size.width,
                                                                                         self.currentDetailViewController.view.frame.size.width);
                                
                                self.button.center = CGPointMake(buttonCenter.x,1000);
                                

                            } completion:^(BOOL finished) {
                                //Remove the old view controller
                                [self.currentDetailViewController removeFromParentViewController];
                                
                                //Set the new view controller as current
                                self.currentDetailViewController = viewController;
                                [self.currentDetailViewController didMoveToParentViewController:self];
                                
                                //reset the button position
                                [UIView animateWithDuration:0.5 animations:^{
                                    self.button.center = buttonCenter;
                                }];
                                
                            }];
}  

In this function we mix the “present” and the “remove” actions using the same functions that I’ve shown you some rows before and using the animateWithDuration:animations:completion function we easily swap the view of the current Detail Controller with the new Detail Controller’s view using some simple animations.

(To see the result you can uncomment the row after “Mode 2” in the function addDetailController and comment the “Mode 1”).

Conclusions

Here’s a simple timeline scheme to resume the steps needed to attach a new child and remove the previous one. This is a common behaviour of almost all the navigation patterns you will build.

1 Current detail controller: [current willMoveToParentViewController:nil]
2 Next detail controller: [container addChildViewController:next]
2.B Next detail controller: [next willMoveToParentViewController:self] (Automatically called by point 2)
3 Next detail controller: [container.view addSubview:next.view]
4 Current detail controller: [current.view removeFromSuperView]
5 Current detail controller: [current removeFromParentViewController]
6 Current detail controller: [current didMoveToParentViewController:nil] (automatically called by point 5)
7 Next detail controller: [next didMoveToParentViewController:self]

You can build any navigation pattern whatsoever just using these “new” functions. Some really good examples of Container Controllers are obviously the UINavigationController and UITabBarController. They have different logics and different usages but they have in common the final result of showing the user one View Controller at a time.

Another example of Container Controller is the UISplitViewController that is able to show two Controllers at the same time .

In a few words, your custom Container has essentially the responsibility to define which controllers have to be shown and how the user can move between Controllers.

I love this approach and I feel safer when I use a Container to build my custom Navigation Pattern because I’m sure that I’m using the right instruments. Plus, using this technique you are absolutely sure that every view of your hierarchy is immediately updated whenever the device changes its orientation.

There you go. Keep in touch on Twitter and feel free to write me if you have questions or suggestions 🙂
Bye!

Yari D'areglia

Yari D'areglia

https://www.thinkandbuild.it

Senior iOS developer @ Neato Robotics by day, game developer and wannabe artist @ Black Robot Games by night.

Navigation