The Easy Way to Switch Container Views in iOS

In my first blog post in this series, I offered a basic way to add a container view to your iOS project. Container views allow you to create a reusable component that other view controllers in your project can share.

Today, I will cover a simple way to switch between two container views—a useful trick when you have to show a different UI at runtime based on the state of your application or you design your app to transition to another view based on user interaction. If you want to follow along, I put the entire project on GitHub.

In this example, I’ll use a segmented control to switch between two container views which I’ll call A and B. I chose a simple fade transition to go from one to another.

Switching Component Views

Build the Example

1. First, add a container view to your view controller. I applied a blue color to the child view controller so you could tell the difference between the two.

2. Next, position and constrain the container view however you want it to appear in your parent view controller.

3. Then drag a second container view right on top of container view A as shown here.

add_container_view_b

4. Constrain container view B to have equal width and height to container view A, and center container view B to the middle of container view A. This way, any layout changes that happen to our first container view will be applied to the second. They should both be the same size and have the same position.

Now you should have two container views with independent child view controllers.  You can customize your child view controllers however you want. I added a purple color to B so you could see the difference.

Indicate Which View to See First

As it stands right now, if you build and run, container view B will be shown on top of container view A. I want to change that so that B is hidden initially and you see A. To do that:

1. Select container view B.

2. Change the alpha property of the view in interface builder from 1 to 0.

This will show container view A at startup and hide B.

finished_adding_container_views

You may be wondering if you could solve this problem by dragging an embed segue to connect the container view with the second child view controller. That way, you would have one container view with two child view controllers. Unfortunately, a container view can have only one embed segue and therefore only one child view controller. Having two container views solves this problem.

To switch between the two views, we have to add some code. Adding an outlet to your view controller for container view A and B lets you change the alpha property on the view that we want to hide/show. On the segmented control used to switch views, I added an action outlet for the “value changed” action called showComponent.


import UIKit

class ViewControllerSwift: UIViewController {
    @IBOutlet weak var containerViewA: UIView!
    @IBOutlet weak var containerViewB: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func showComponent(sender: UISegmentedControl) {
        if sender.selectedSegmentIndex == 0 {
            UIView.animateWithDuration(0.5, animations: {
                self.containerViewA.alpha = 1
                self.containerViewB.alpha = 0
            })
        } else {
            UIView.animateWithDuration(0.5, animations: {
                self.containerViewA.alpha = 0
                self.containerViewB.alpha = 1
            })
        }
    }
}

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *containerViewA;
@property (weak, nonatomic) IBOutlet UIView *containerViewB;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (IBAction)showComponent:(UISegmentedControl *)sender {
    if (sender.selectedSegmentIndex == 0) {
        [UIView animateWithDuration:(0.5) animations:^{
            self.containerViewA.alpha = 1;
            self.containerViewB.alpha = 0;
        }];
    } else {
        [UIView animateWithDuration:(0.5) animations:^{
            self.containerViewA.alpha = 0;
            self.containerViewB.alpha = 1;
        }];
    }
}

@end

When the selectedSegmentIndex is zero, we want to display container view A. Otherwise, we want to show B. I used a simple UIView animation to animate the alpha of the two component views. The animation is optional. You could set the alpha property directly.

The Downside

This solution is pretty simple, but it does come at a cost in terms of memory usage. Both of the child view controllers for A and B are in memory the whole time your parent view controller is alive–even if you decide to only show one of them at runtime. Also, all of the rotation and appearance messages (such as viewDidAppear) received by the parent view controller are automatically delegated to the child view controllers. Since both view controllers are in the view hierarchy, they both receive these events. This could cause performance problems if you have time-consuming code. However, in most situations, these downsides are not an issue. In my next blog post, I will show you a way to avoid some of these downsides. If you would like to try this code out yourself to see it in action, I have the entire project on GitHub.


Catch up on my other posts in this series:


For more on using Xcode, read my series on unwind segues:

Conversation
  • Camilo says:

    Thanks, it seems like a simple way for switching container views.

    Also I found this way too.
    http://sandmoose.com/post/35714028270/storyboards-with-custom-container-view-controllers

    it uses empty segues :)

  • Panhna says:

    Hi Mike. i am a beginner IOS developer. i want to ask you how can we access the element (textfield, …) on Comp A or B from First View Controller?

    • Mike Woelmer Mike Woelmer says:

      Hi Panhna,

      In my example I store a reference to componentA and B using a generic UIView*. You can either cast those pointers to your custom type or you could change the variable type to be your custom type.

      • Panhna says:

        thx Mike. it is a little bit complicate to use Container View may be i am the beginner with ios. now i use View Element instead of Container View. it work well and easy to use for beginner like me. hehehehhe.

        how ever thx for your reply. i may be try to use it again when i get out from beginner.

  • Rohan Patel says:

    Mike,

    My question is probably dumb but I am wondering if there is a way to include navigation controller in container view or that will complicate the view hierarchy?

    • Mike Woelmer Mike Woelmer says:

      Hi Rohan,

      You can only add a UINavigationController to a UIViewController — not a container within. However you can add a custom UINavigationBar to a component and re-use that navigation component in every scene of your application. Navigation controls are a good use of components.

      • Shrikant says:

        Hello Mike,
        I am also a beginner. Can you please explain the use of Navigation controller in detail.
        I am trying to develop an app with Left side out menu. I’m unable to link using buttons provided on each view as well as the menu options with the different view controllers.
        Thanks,
        Shrikant

  • Joan says:

    Is it possible to do it without storyboard? How would you do it (I mean, which architecture would you follow as Container is not available).

    Thanks

    • Mike Woelmer Mike Woelmer says:

      Hi Joan,

      Anything you can do in the storyboards can be done in code. Although visually you see the container in the storyboard — internally it is nothing more than a UIView with child views.

  • Duc says:

    Hi Mike Woelmer!
    your way can help me in situation with 4 viewcontroller all of map. I want at once time only have 1 viewcontroller in containview appear. in other state i want replace current viewcontroller by other controller. Can u give me a suggetion?

  • Jason Foster says:

    I have tried implementing this code, but I seem to be running into an infinite loop! Any ideas people?

  • hengchengfei says:

    nice tutorial!

    but viewWillAppear loaded only once,even while switch two viewcontroller.

    • Mike Woelmer Mike Woelmer says:

      Yes, with this solution, both child view controllers are in the view hierarchy at all times. One option you have is you can create a function in your child view controller that you call when you do the transition if you want to notify the child view controller that it is about to be in view.

  • Vivek Gani says:

    Thanks for this guide, I made a sample project based on this for OSX / mac here: https://github.com/seltzered/OSXStoryboardWizardBootstrap

  • John Beadle says:

    Hi Mike,

    Thanks for sharing this useful information.

    May I ask for some help, I have a view controller inside a container view within which I am trying to create a WKWebView and load a webpage inside. However it doesn’t seem to be loading the page given in my NSURL.

    Is there anything special I need to do when using container views and WKWebViews ?

    Thanks for your time,
    John

  • HideCode says:

    Many congratulations for the tutorial is impeccable. However I have a problem my container is not to use the UIViewController in full and when i put constrains the 0,0,0,0 this is red and gives error .

    How can I fix this ?
    Already now intended to add a scroll to the container is possible?

    Thank you so much

    • Mike Woelmer Mike Woelmer says:

      Hi, I put the project on GitHub, so you can see the differences between your code and mine.

  • Sasi says:

    if there is an 3 container what is the Alpha values for each container

    • Soniu says:

      same like with 2. You have one ViewController you want to show so that the alpha value of that one is 1.0. All others should not be viewable so you set them to 0.0

  • Lawrence says:

    Worst design ever – a man does not want to load one view container for each view to be contained.

  • John Armstrong says:

    This seems to work great for view switching but the underneath view is never event accessible since events are intercepted by the higher level view, even if its transparent.

    Is there a workaround for this other then overriding hitTest?

  • Gressquel says:

    Bad solution.
    Not only is there memory and performance issues here, but you will stumble upon random touch problems.
    For example, I had a view which had UIcollection with images. Using this solution created strange touch area issues. I clicked on a cell, a different cell got selected.

  • John Dunne says:

    Nice post! The extra memory usage is worth the simplicity of the solution :)

  • Slava says:

    Mike, I’m working as an iOS developer for around 3 years and don’t really remember how much times I faced your article while I was looking for inspirational ideas.
    Just wanted to thank you (and happy NY!) :)

  • Greg says:

    Re performance/touch issues people have touched on, does anyone have any direction/hints/links on how to address this, but in a fashion that doesn’t require the who contained view’s view controllers to have to restart each time? i.e. what to swap back and forth rapidly between the two view controllers (views) [e.g. one might be a calendar and the other a map] but don’t want to have to redraw map route each time

  • xc says:

    are you kidding? hope nobody does in this way…

    • Mohamed says:

      do you better way ? if so, please forward a link!

    • Muhammad Bilal Azhar says:

      Do you have a better solution than this ? If yes than send me please

  • Muhammad Bilal Azhar says:

    Hey can you please send me the link of the post where you addressed memory issues and other downsides ?
    That would be great . Or if there is another better way to do this .

  • joes says:

    I do this kind of thing by adding subviews from the view of each view controller into a UIStackView (so that I don’t have to configure constraints for each vc):

    class SomeParentVC: UIViewController {
    @IBOutlet weak var segmentedCtrl: UISegmentedControl!
    @IBOutlet weak var stackView: UIStackView!
    var vc1: CustomVC1?
    var vc2: CustomVC2?

    override func viewDidLoad() {
    vc1 = CustomVC1.getInstance() //static method in the CustomVC1.swift
    vc2 = CustomVC2.getInstance() //static method in the CustomVC2.swift

    //load initial vc
    loadViewFrom(vc: vc1!)
    }

    @IBAction func segmentClicked(_ sender: UISegmentedControl) {
    loadViewFrom(vc: (sender.selectedSegmentIndex == 0 ? vc1! : vc2!))
    }

    func loadViewFrom(vc: UIViewController) {
    let vcToRemove = vc == vc1 ? vc2! : vc1!
    if stackView.arrangedSubviews.count > 0 {
    vcToRemove.view.removeFromSuperview()
    vcToRemove.removeFromParentViewController()
    }

    self.addChildViewController(vc)
    self.stackView.addArrangedSubview((vc.view)!)
    }
    }

    When you use the view from each vc the first time, viewDidLoad() will get called for each view controller. Then as you toggle back and forth using the segmented control, you can use viewWillAppear() to do additional business logic for each controller. That’s pretty much it.

    • joes says:

      You don’t need to add the UISegmentedControl if you don’t need it. It’s in the code above because I was using it for something else in the test.

  • Comments are closed.