51

Is there a way to have a single ContainerView with multiple embed segues? The aim is for a ContainerView to hold a few different ViewControllers depending on what buttons have been pressed; only one is going to be visible at a time. I want to use embed segues so that in Interface Builder the storyboards are automatically shown at the same size as the ContainerView.

I realise that I can manually resize the other ViewControllers in InterfaceBuilder, but I want the automatic sizing provided by the embed segue. If another way of doing that is available, that would be fine too. Not having the views load on viewDidLoad is fine - as mentioned earlier, the shown ViewController can change depending on the buttons pressed.

Steve Haley
  • 55,374
  • 17
  • 77
  • 85
  • 1
    I realise this isn't what you asked, but as another workaround, how about having multiple ContainerViews, overlapping and identical sizes, each with their own custom segue. Then, in code, perform the correct segue using `[self performSegueWithIdentifier:]` depending upon which child UIViewController you want displayed. At least, this way, you keep your auto-sizing, you keep your connections in IB and you keep your `prepareForSegue` logic. – Carlos P Aug 12 '15 at 19:41

7 Answers7

50

No, there is no way to have multiple embed segues to one container view. One way to do all the setup in IB, would be to make the embedded controller a UITabBarController (with the tab bar hidden). You can then have as many controllers in the tabs as you want, and switch to them in code using the selectedIndex property of UITabBarController.

rdelmar
  • 103,982
  • 12
  • 207
  • 218
  • I looked around quite a bit before asking, but I suppose I kept hoping I'd missed something. Oh well, I'll just live with the views being the wrong size. (Your idea about using the TabBarController is interesting, but unfortunately my ContainerView is already is another TabBarController). – Steve Haley Aug 23 '13 at 17:24
  • 14
    I realise this is an old answer. The way I solved this, without really thinking much about it was to have multiple view containers and only one visible at the time. Don't know if it's the best way, but It works fine. – Nuno Gonçalves Oct 02 '15 at 13:19
  • This is absolutely the best way to do this for two reasons, imo: 1) It is easy to understand visually on the storyboard. 2) It is much simpler and understandable to code. Thanks [rdelmar](http://stackoverflow.com/users/1285133/rdelmar). This was way too hidden on Google search. – Chucky Jun 10 '16 at 10:09
  • @NunoGonçalves how exactly u achieved this ? could please help me in this? – Charmi Jul 12 '17 at 15:38
  • @Charmi This is for over a year ago. But I guess I added 2 containers, one segue for each, and hide each container as needed. Hiding the container will hide the content obviously, so I got the behaviour I desired. – Nuno Gonçalves Jul 12 '17 at 16:38
  • This is an elegant solution. I'm using it. If anyone needs help hiding the Tab bar, I have a solution over on this question: https://stackoverflow.com/questions/15296065/ios-tabbarviewcontroller-hide-the-tab-bar/45291118#45291118 – Mark Suman Jul 24 '17 at 22:36
  • I strongly recommend looking at the [next solution](https://stackoverflow.com/a/27920691/558006) instead (from @Kanjiroushi). Using a tab view controller doesn't work in my situation, because it keeps all the view controllers in each tab alive, whereas I want each destroyed after use and only recreated when needed. – brotskydotcom Feb 23 '19 at 10:41
17

Yes, I was able to achieve what you're looking for inspired by @rdelmar post. What you need to do is to embed a UITabBarViewController into your container view. Then you programmatically choose which controller you like to present. You might also like to hide the tab bar.

Container view indirectly containing more views

If you want you can also hide the tab bars seen in the storyboard file

You can choose the view controller you want to present by subclassing the UITabBarController:

override func viewDidLoad() {
    super.viewDidLoad()
    self.selectedIndex = 1
}

You can hide the tab bar in your view controller by calling self.tabBarController?.tabBar.isHidden = true in viewDidLoad().

pableiros
  • 14,932
  • 12
  • 99
  • 105
Andrej
  • 7,266
  • 4
  • 38
  • 57
  • This was useful, but how do I select an item from my main VC (That contains the ContainerView)? I have tried putting `self.tabBarController?.selectedIndex = 5` in `viewDidLoad()` but it does not work ): – Arnaldo Dec 16 '16 at 21:56
  • 2
    You do that in 'prepareForSegue' method. Something like this: 'guard let tabBarVC = segue.destination as? UITabBarController else {return}' and then 'tabBarVC.selectedIndex = 5'. Maybe I've made some errors since I'm typing on the phone, but hope the idea is clear. – Andrej Dec 16 '16 at 22:14
  • Thanks, bro! Helped me. – Arnaldo Dec 19 '16 at 20:45
15

I recognize this question is a bit old, but I wanted to answer in case you're still looking or other people find this. I had a similar issue and I worked around it.

In short, you'll have three layers:
- an external view controller ("ExternalViewController")
- a view controller manager ("ViewControllerManager")
- the child view controllers you actually want to be switching between ("ChildViewController")

Use a container view in ExternalViewController with an embed segue to the ViewControllerManager. ViewControllerManager itself would then hold other ChildViewControllers programmatically as described in this Apple documentation, specifically the section on adding and removing a child.

When you add a child view controller, set its frame to be the same as the ViewControllerManager's frame (since you're doing this inside the ViewControllerManager, set the child's frame equal to self.view.frame). You will also need some logic and an external control to do the switching inside of ExternalViewController, of course.

Hope this helps!

UberJason
  • 3,063
  • 2
  • 25
  • 50
14

I found this wonderful article that explained exactly how to do it: http://sandmoose.com/post/35714028270/storyboards-with-custom-container-view-controllers

you get your container and can call any view controller behind, there is a bit of set up to have everything linked but once done, you get a still useable storyboard.

Kanjiroushi
  • 223
  • 2
  • 4
  • This the answer yes it is possible according to the link – 0xFK Jan 20 '15 at 19:31
  • 1
    This works better for me. The major downside of the tab view controller is that the VC's for each tab continue to live when not visible. When they are not visible, I want it to deinit and then reinitialize from scratch if it needs to come back. – Chuck H Sep 08 '16 at 02:30
  • 2
    Link is sold out :( – jeet.chanchawat Jul 30 '18 at 11:14
  • 4
    I found the link content above using the wayback machine at [this link](https://web.archive.org/web/20170605234158/http://sandmoose.com/post/35714028270/storyboards-with-custom-container-view-controllers). It's a terrific solution, and the author posted the code in a [github project](https://github.com/mluton/EmbeddedSwapping) that is still live. – brotskydotcom Feb 23 '19 at 10:33
4

I've achieved that by making use of -shouldPerformSegueWithIdentifier:sender:. I have a container that is passed an object and depending on the type of this object decides which child view controller to show.

The structure seems a little over complicated but allows the base view controller to ignore the different types of tasks I have, leaving that to the container view controller. The container view controller has then multiple container views which segues are only performed depending on the type of task.

I don't know if you can actually perform the embed segues manually by calling -performSegueWithIdentifier:sender: but that could also be a possibility.

Fábio Oliveira
  • 2,346
  • 21
  • 30
2

I struggled with this for a long time, too. I had the case where I had different, but similar embedded table view controllers that I wanted to show depending on a parameter that was set in the segue to the view controller. What worked was to put in the embedded container with an IBOutlet in the view controller. The container can have size constraints set in IB. However, don't make any embed segues in IB. Then in viewDidLoad, I programmatically add the correct view controller and pin its edges to the embed container.

The heart of this approach is seen in the following code (Swift 4):

extension UIView {
    func pinToParent() {
        self.translatesAutoresizingMaskIntoConstraints = false
        let attributes: [NSLayoutAttribute] = [.top, .bottom, .right, .left]
        NSLayoutConstraint.activate(attributes.map {
            NSLayoutConstraint(item: self, attribute: $0, relatedBy: .equal, toItem: self.superview, attribute: $0, multiplier: 1, constant: 0)
        })
    }
}

class ColorVC: UIViewController {

    @IBOutlet weak var tableContainer: UIView!
    var color : rgb = .red

    fileprivate var colorTableVC : ColorTableVC?

    override func viewDidLoad() {
        super.viewDidLoad()

        switch color {
        case .red:
            colorTableVC = RedTableVC.init(style: .plain)
        case .green:
            colorTableVC = GreenTableVC.init(style: .plain)
        case .blue:
            colorTableVC = BlueTableVC.init(style: .plain)
        }
        if let vc = colorTableVC {
            if (vc.view) != nil {
                self.addChildViewController(vc)
                tableContainer.addSubview(vc.view)
                vc.view.pinToParent()
                vc.didMove(toParentViewController: self)
            }
        }
    }
}

In the ColorVC, one sees the container IBOutlet and the "color" parameter set by the main table view controller. The RedTableVC, GreenTableVC, and BlueTableVC are all subclassed from ColorTableVC which is sub-classed from UITableViewController. The common heritage lets me use one "colorTableVC" variable to point to any of the instantiated controllers. (Not entirely necessary). But this does avoid duplicating code below to add the view in the heirarchy and pin the new controller to the container view. At the top, I made an extension on UIView to pin a view to its parents edges.

The following image shows how the project and particularly the view controller on the right was set up in IB. For this example, I made the height of the "embedded" controller half the height of the main view controller - so when you rotate the device, one can see that the constraints set in IB are indeed applied.

Embedded controller IB setup

anorskdev
  • 1,867
  • 1
  • 15
  • 18
-1

In your working viewController drag a uiview and set constraint to top, leading, trailing, bottom to safe area (Iphone X serise).

Now put Container view (Content View) inside that UIView. Put as many content views as you want inside that UIView and embed to respective ViewContrllers.

Worked for me.

JobLess
  • 67
  • 3