Change Container View Content with Tabs in iOS
Asked Answered
T

5

16

I'm trying to make a form that spans three tabs. You can see in the screenshot below where the tabs will be. When the user taps a tab, the Container View should update and show a particular view controller I have.

view controller

Tab 1 = View Controller 1

Tab 2 = View Controller 2

Tab 3 = View Controller 3

The view controller shown above has the class PPAddEntryViewController.m. I created an outlet for the Container view within this class and now have a Container View property:

@property (weak, nonatomic) IBOutlet UIView *container;

I also have my IBActions for my tabs ready:

- (IBAction)tab1:(id)sender {
  //...
}
- (IBAction)tab2:(id)sender {
  //...
}
- (IBAction)tab3:(id)sender {
  //...
}

How do I set the container in those IBActions to change the view controller that the Container View holds?

Among a few other things, here's what I've tried:

UIViewController *viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:@"vc1"];
_container.view = viewController1;

...but it doesn't work. Thanks in advance.

Theatricals answered 2/5, 2013 at 4:9 Comment(1)
Go through this link ... may helpful.. cocoanetics.com/2012/04/containing-viewcontrollersWaters
T
10

I recently found the perfect sample code for what I was trying to do. It includes the Storyboard implementation and all the relevant segues and code. It was really helpful.

https://github.com/mhaddl/MHCustomTabBarController

Theatricals answered 10/5, 2013 at 3:58 Comment(1)
missing disclaimer and is a link only answer bothCb
T
16

Switching using Storyboard, Auto-layout or not, a Button of some sort, and a series of Child View Controllers

You want to add the container view to your view and when the buttons that 'switch' child view controllers are pressed fire off the appropriate segue and perform the correct setup work.

In the Storyboard you can only connect one Embed Segue to the Container View. So you create an intermediate handling controller. Make the embed segue and give it an identifier, for example EmbededSegueIdentifier.

In your parent view controller wire up the button or whatever you want and keep are reference to your child view controller in the prepare segue. As soon as the parent view controller loads the segue will be fired.

The Parent View Controller

@property (weak, nonatomic) MyContainerViewController *myContainerViewController;

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"EmbeddedSegueIdentifier"]) {
        self.myContainerViewController = segue.destinationViewController;
    }
}

It should be fairly easy for you to delegate to your container controller the button presses.

The Container Controller

This next bit of code was partly borrowed from a couple of sources, but the key change is that auto layout is being used as opposed to explicit frames. There is nothing preventing you from simply changing out the lines [self addConstraintsForViewController:] for viewController.view.frame = self.view.bounds. In the Storyboard this Container View Controller doesn't do anything more that segue to the destination child view controllers.

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSLog(@"%s", __PRETTY_FUNCTION__);

    [self performSegueWithIdentifier:@"FirstViewControllerSegue" sender:nil];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    UIViewController *destinationViewController = segue.destinationViewController;

    if ([self.childViewControllers count] > 0) {
        UIViewController *fromViewController = [self.childViewControllers firstObject];
        [self swapFromViewController:fromViewController toViewController:destinationViewController];
    } else {
        [self initializeChildViewController:destinationViewController];
    }
}

- (void)initializeChildViewController:(UIViewController *)viewController
{
    [self addChildViewController:viewController];
    [self.view addSubview:viewController.view];
    [self addConstraintsForViewController:viewController];

    [viewController didMoveToParentViewController:self];
}

- (void)swapFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
    [fromViewController willMoveToParentViewController:nil];
    [self addChildViewController:toViewController];
    [self transitionFromViewController:fromViewController toViewController:toViewController duration:0.2f options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) {
        [self addConstraintsForViewController:toViewController];
        [fromViewController removeFromParentViewController];
        [toViewController didMoveToParentViewController:self];
    }];
}

- (void)addConstraintsForViewController:(UIViewController *)viewController
{
    UIView *containerView = self.view;
    UIView *childView = viewController.view;
    [childView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [containerView addSubview:childView];

    NSDictionary *views = NSDictionaryOfVariableBindings(childView);
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[childView]|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[childView]|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];
}



#pragma mark - Setters

- (void)setSelectedControl:(ViewControllerSelectionType)selectedControl
{
    _selectedControl = selectedControl;

    switch (self.selectedControl) {
        case kFirstViewController:
            [self performSegueWithIdentifier:@"FirstViewControllerSegue" sender:nil];
            break;
        case kSecondViewController:
            [self performSegueWithIdentifier:@"SecondViewControllerSegue" sender:nil];
            break;
        default:
            break;
    }
}

The Custom Segues

The last thing you need is a custom segue that does nothing, going to each destination with the appropriate segue identifier that is called from the Container View Controller. If you don't put in an empty perform method the app will crash. Normally you could do some custom transition animation here.

@implementation SHCDummySegue

@interface SHCDummySegue : UIStoryboardSegue

@end

- (void)perform
{
    // This space intentionally left blank
}

@end
Teenateenage answered 13/1, 2014 at 12:31 Comment(1)
very nice and generic solution that works not only for replacing a UITabbarController!Distilled
T
10

I recently found the perfect sample code for what I was trying to do. It includes the Storyboard implementation and all the relevant segues and code. It was really helpful.

https://github.com/mhaddl/MHCustomTabBarController

Theatricals answered 10/5, 2013 at 3:58 Comment(1)
missing disclaimer and is a link only answer bothCb
P
7

Update: UITabBarController is the recommended way to go, as you found out earlier. In case you'd like to have a custom height, here is a good start: My way of customizing UITabBarController's tabbar - Stackoverflow answer

As of iOS 5+ you have access to customize the appearance via this API; UIAppearance Protocol Reference. Here is a nice tutorial for that: How To Customize Tab Bar Background and Appearance

The most obvious way to achieve what you're looking for is to simply manage 3 different containers (they are simple UIViews) and implement each of them to hold whatever content view you need for each tab (use the hidden property of the containers).

Here is an example of what's possible to achieve with different containers:

Embed View Controllers

These containers "swapping" can be animated of course. About your self-answer, you probably chose the right way to do it.

Pairoar answered 3/5, 2013 at 6:22 Comment(5)
he isn't using a tab bar controller AFAIK that's why the container view is thereCb
@Cb I don't think I mentioned tab bar controller. I see tabs only. The point is, the container view is not meant to have it's embed view controller swapped like he seems to want to.Pairoar
I thought about just showing/hiding views all within the same view controller, but as a Storyboard user, it's more convenient if I can keep each of the 3 views as separate view controllers. This would allow me to style each of the view controllers separately and be able to see its layout (having hidden views wouldn't be quite as convenient).Theatricals
@Roger: ": the container view would get a new Parent everytime" -> no ONLY if there was tabviewcontroller involved that would switch the container. the idea here is to switch the container contentCb
@CliftonLabrum Not sure if it's you who down-voted, but let me clarify my answer so may be it get more understandable. I will also update my answer with a concrete example of what I'm saying.Pairoar
C
2

have a member variable to hold the viewController:

UIViewController *selectedViewController;

now in the IBActions, switch that AND the view. e.g.

- (IBAction)tab1:(id)sender {
     UIViewController *viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:@"vc1"];

     _container.view = viewController1.view;
     selectedViewController = viewController1;
}

to fire view did appear and stuff call removeChildViewController, didMoveToParent, addChildViewController, didMoveToParent

Cb answered 3/5, 2013 at 7:1 Comment(3)
Can you help me understand what selectedViewController = viewController1; is for?Theatricals
I continue to have trouble getting this to work. Is there any way you can throw together a quick Xcode project that swaps the content of a UIContainerView between two separate viewControllers in a Storyboard? I think I have it wired up correctly, but I'm unable to get the view in the ControllerView to change.Theatricals
This solution works! Just make sure to save to store selectedViewController as this example illustrates. I skipped that part and kept getting memory misses. That's because it was released after tab1 was finshed, which prevented my new controller from working at all.Vernita
T
0

I got this to work by using a UITabBarController. In order to use custom tabs, I had to subclass the TabBarController and add the buttons to the controller in code. I then listen for tap events on the buttons and set the selectedIndex for each tab.

It was pretty straight forward, but it's a lot of junk in my Storyboard for something as simple as 3 tabs.

Theatricals answered 4/5, 2013 at 6:20 Comment(3)
yes that is the way it is meant to be ... though I also often use the approach of just switching out views like I outlined..Cb
I tried doing this with a manual approach of switching out views like you mentioned, and I actually like it better. Then there's only a single navigation controller at play, one Save button, and a lot less redundancy in my storyboard. I think I'm going to go with what you mentioned after all. I don't see your answer anymore, but thanks for chiming in!Theatricals
my answer is at the bottom and starts with 'have a member variable ...' :)Cb

© 2022 - 2024 — McMap. All rights reserved.