Changing view controller when Segmented Control changes

21,441

Solution 1

I'd say it's much simpler to change subviews within a UIViewController, you can set up your subviews in your storyboards and hook them up with IBOulets in your controller you can set the hidden property of your views to YES or NO depending on the control that was clicked.

Now, if you use @Robotic Cat's approach which is also a good solution you can have a little more modularity in how your app works, considering you'd have to place all your logic in one controller using the solution I presented.

Solution 2

NOTE: Answer updated with view controller containment code for iOS 5+ including @interface section

In an app of mine, I have a view controller with a Segment Control in the Navigation Bar and clicking on the "tabs" switches view controllers. The basic idea is to have an array of view controllers and switch between them using the Segment Index (and the indexDidChangeForSegmentedControl IBAction.

Example code (iOS 5 or later) from my app (this is for 2 view controllers but it's trivially extended to multiple view controllers); the code is slightly longer than for iOS 4 but will keep the object graph intact. Also, it uses ARC:

@interface MyViewController ()
// Segmented control to switch view controllers
@property (weak, nonatomic) IBOutlet UISegmentedControl *switchViewControllers;

// Array of view controllers to switch between
@property (nonatomic, copy) NSArray *allViewControllers;

// Currently selected view controller
@property (nonatomic, strong) UIViewController *currentViewController;
@end

@implementation UpdateScoreViewController
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];

    // Create the score view controller
    ViewControllerA *vcA = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewControllerA"];

    // Create the penalty view controller
    ViewControllerB *vcB = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewControllerB"];

    // Add A and B view controllers to the array
    self.allViewControllers = [[NSArray alloc] initWithObjects:vcA, vcB, nil];

    // Ensure a view controller is loaded
    self.switchViewControllers.selectedSegmentIndex = 0;
    [self cycleFromViewController:self.currentViewController toViewController:[self.allViewControllers objectAtIndex:self.switchViewControllers.selectedSegmentIndex]];
}

#pragma mark - View controller switching and saving

- (void)cycleFromViewController:(UIViewController*)oldVC toViewController:(UIViewController*)newVC {

    // Do nothing if we are attempting to swap to the same view controller
    if (newVC == oldVC) return;

    // Check the newVC is non-nil otherwise expect a crash: NSInvalidArgumentException
    if (newVC) {

        // Set the new view controller frame (in this case to be the size of the available screen bounds)
        // Calulate any other frame animations here (e.g. for the oldVC)
        newVC.view.frame = CGRectMake(CGRectGetMinX(self.view.bounds), CGRectGetMinY(self.view.bounds), CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds));

        // Check the oldVC is non-nil otherwise expect a crash: NSInvalidArgumentException
        if (oldVC) {

            // Start both the view controller transitions
            [oldVC willMoveToParentViewController:nil];
            [self addChildViewController:newVC];

            // Swap the view controllers
            // No frame animations in this code but these would go in the animations block
            [self transitionFromViewController:oldVC
                              toViewController:newVC
                                      duration:0.25
                                       options:UIViewAnimationOptionLayoutSubviews
                                    animations:^{}
                                    completion:^(BOOL finished) {
                                        // Finish both the view controller transitions
                                        [oldVC removeFromParentViewController];
                                        [newVC didMoveToParentViewController:self];
                                        // Store a reference to the current controller
                                        self.currentViewController = newVC;
                                    }];

        } else {

            // Otherwise we are adding a view controller for the first time
            // Start the view controller transition
            [self addChildViewController:newVC];

            // Add the new view controller view to the ciew hierarchy
            [self.view addSubview:newVC.view];

            // End the view controller transition
            [newVC didMoveToParentViewController:self];

            // Store a reference to the current controller
            self.currentViewController = newVC;
        }
    }
}

- (IBAction)indexDidChangeForSegmentedControl:(UISegmentedControl *)sender {

    NSUInteger index = sender.selectedSegmentIndex;

    if (UISegmentedControlNoSegment != index) {
        UIViewController *incomingViewController = [self.allViewControllers objectAtIndex:index];
        [self cycleFromViewController:self.currentViewController toViewController:incomingViewController];
    }

}
@end

Original example (iOS 4 or before):

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];

    // Create the score view controller
    AddHandScoreViewController *score = [self.storyboard instantiateViewControllerWithIdentifier:@"AddHandScore"];

    // Create the penalty view controller
    AddHandPenaltyViewController *penalty = [self.storyboard instantiateViewControllerWithIdentifier:@"AddHandPenalty"];

    // Add Score and Penalty view controllers to the array
    self.allViewControllers = [[NSArray alloc] initWithObjects:score, penalty, nil];

    // Ensure the Score controller is loaded
    self.switchViewControllers.selectedSegmentIndex = 0;
    [self switchToController:[self.allViewControllers objectAtIndex:self.switchViewControllers.selectedSegmentIndex]];
}

#pragma mark - View controller switching and saving

- (void)switchToController:(UIViewController *)newVC
{
    if (newVC) {
        // Do nothing if we are in the same controller
        if (newVC == self.currentViewController) return;

        // Remove the current controller if we are loaded and shown
        if([self.currentViewController isViewLoaded]) [self.currentViewController.view removeFromSuperview];

        // Resize the new view controller
        newVC.view.frame = CGRectMake(CGRectGetMinX(self.view.bounds), CGRectGetMinY(self.view.bounds), CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds));

        // Add the new controller
        [self.view addSubview:newVC.view];

        // Store a reference to the current controller
        self.currentViewController = newVC;
    }
}

- (IBAction)indexDidChangeForSegmentedControl:(UISegmentedControl *)sender {

    NSUInteger index = sender.selectedSegmentIndex;

    if (UISegmentedControlNoSegment != index) {
        UIViewController *incomingViewController = [self.allViewControllers objectAtIndex:index];
        [self switchToController:incomingViewController];
    }

}
Share:
21,441
Ricardo Macario
Author by

Ricardo Macario

Updated on August 06, 2022

Comments

  • Ricardo Macario
    Ricardo Macario almost 2 years

    This problem is driving me crazy. I'm trying to change the viewController when the user changes the selected "tab" of the segmented control. I've spent a couple hours researching and haven't been able to find an answer that works or is done through storyboard.

    It really bother me since setting a tab application is so easy, but trying to use the segmented control like the tab application is just not working. I already know how to detect which index is selected in the segmented control. How can I achieve this?

    Thank you very much.