Switching ViewControllers with UISegmentedControl in iOS5

Solution 1:

This code works pretty well for your purpose, I use it for one of my new apps.
It uses the new UIViewController containment APIs that allow UIViewControllers inside your own UIViewControllers without the hassles of manually forwarding stuff like viewDidAppear:

- (void)viewDidLoad {
    [super viewDidLoad];
    // add viewController so you can switch them later. 
    UIViewController *vc = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
    [self addChildViewController:vc];
    vc.view.frame = self.contentView.bounds;
    [self.contentView addSubview:vc.view];
    self.currentViewController = vc;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
    UIViewController *vc = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
    [self addChildViewController:vc];
    [self transitionFromViewController:self.currentViewController toViewController:vc duration:0.5 options:UIViewAnimationOptionTransitionFlipFromBottom animations:^{
        [self.currentViewController.view removeFromSuperview];
        vc.view.frame = self.contentView.bounds;
        [self.contentView addSubview:vc.view];
    } completion:^(BOOL finished) {
        [vc didMoveToParentViewController:self];
        [self.currentViewController removeFromParentViewController];
        self.currentViewController = vc;
    }];
    self.navigationItem.title = vc.title;
}

- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
    UIViewController *vc;
    switch (index) {
        case 0:
            vc = [self.storyboard instantiateViewControllerWithIdentifier:@"FooViewController"];
            break;
        case 1:
            vc = [self.storyboard instantiateViewControllerWithIdentifier:@"BarViewController"];
            break;
    }
    return vc;
}

I got this stuff from chapter 22 of Ray Wenderlichs book iOS5 by tutorial. Unfortunately I don't have a public link to a tutorial. But there is a WWDC 2011 video titled "Implementing UIViewController Containment"

EDIT

self.typeSegmentedControl is outlet for your UISegmentedControl

self.contentView is outlet for your container view

self.currentViewController is just a property that we're using to store our currently used UIViewController

Solution 2:

It's Matthias Bauch solution, but thought of sharing it in Swift anyway! Edit: Adding a link to a Swift 2.0 ready made demo app. https://github.com/ahmed-abdurrahman/taby-segmented-control

var currentViewController: UIViewController?
@IBOutlet weak var contentView: UIView!
@IBOutlet weak var segmentedControl: UISegmentedControl!

@IBAction func switchHappeningTabs(sender: UISegmentedControl) {
    if let vc = viewControllerForSelectedSegmentIndex(sender.selectedSegmentIndex) {
        self.addChildViewController(vc)
        self.transitionFromViewController(self.currentViewController!, toViewController: vc, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {
            self.currentViewController!.view.removeFromSuperview()
            vc.view.frame = self.contentView.bounds
            self.contentView.addSubview(vc.view)
            }, completion: { finished in
                vc.didMoveToParentViewController(self)
                self.currentViewController!.removeFromParentViewController()
                self.currentViewController = vc
            }
        )        
    }
}

override func viewDidLoad() {
    super.viewDidLoad()

    if let vc = self.viewControllerForSelectedSegmentIndex(self.segmentedControl.selectedSegmentIndex) {
        self.addChildViewController(vc)
        self.contentView.addSubview(vc.view)
        self.currentViewController = vc
    }
}

func viewControllerForSelectedSegmentIndex(index: Int) -> UIViewController? {
    var vc: UIViewController?
    switch index {
    case 0:
            vc = self.storyboard?.instantiateViewControllerWithIdentifier("FooViewController") as? UIViewController
    case 1:
            vc = self.storyboard?.instantiateViewControllerWithIdentifier("BarViewController") as? UIViewController
    default:
        return nil
    }

    return vc
}

Solution 3:

For someone want to implement the same in swift

import UIKit

class HomeController: UIViewController {

    var currentViewController:UIViewController?

    @IBOutlet weak var homeController: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let initialController:UIViewController = self.viewControllerForSegmentedIndex(0)

        self.addChildViewController(initialController)

        initialController.view.frame = self.homeController.bounds
        self.homeController.addSubview(initialController.view)
        self.currentViewController = initialController

    }

    @IBAction func segmentChanged(sender: UISegmentedControl) {

        let viewCOntroller:UIViewController = viewControllerForSegmentedIndex(sender.selectedSegmentIndex)

        self.addChildViewController(viewCOntroller)

        self.transitionFromViewController(self.currentViewController!, toViewController: viewCOntroller, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromBottom, animations: {
            self.currentViewController?.view.removeFromSuperview()
            viewCOntroller.view.frame = self.homeController.bounds
            self.homeController.addSubview(viewCOntroller.view)

            }, completion:{ finished in

                viewCOntroller.didMoveToParentViewController(self)
                self.currentViewController?.removeFromParentViewController()
                self.currentViewController = viewCOntroller

        })

    }
    func viewControllerForSegmentedIndex(index:Int) -> UIViewController {
        var viewController:UIViewController?
        switch index {
        case 0:
            viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForFirstController")
            break
        case 1:
            viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForSecondController")
            break
        case 2:
            viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForThirdController")
            break
        default:
            break
        }
        return viewController!
    }
}

Storyboard view

enter image description here

Solution 4:

A is root view controller, and B, C is sub view controller. When you click segment, add sub view controller.

result

result

design

design

code

import UIKit

class SegmentViewController: UIViewController {

    @IBOutlet weak var containerView: UIView!

    var leftViewController: LeftViewController!
    var rightViewController: RightViewController!


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        if let sb = storyboard {
            leftViewController = sb.instantiateViewControllerWithIdentifier("leftViewController") as! LeftViewController

            switchViewController(from: nil, to: leftViewController)
        } else {
            print("storyboard is nil")
        }
    }

    func switchViewController(from fromVC: UIViewController?, to toVC: UIViewController?) {
        if let from = fromVC {
            from.willMoveToParentViewController(nil)
            from.view.removeFromSuperview()
            from.removeFromParentViewController()
        } else {
            print("fromVC is nil")
        }

        if let to = toVC {
            self.addChildViewController(to)
            to.view.frame = CGRectMake(0, 0, containerView.frame.width, containerView.frame.height)
            self.containerView.insertSubview(to.view, atIndex: 0)
            to.didMoveToParentViewController(self)
        } else {
            print("toVC is nil")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.

        removeViewController()
    }

    func removeViewController() {
        if let leftVC = leftViewController {
            if let _ = leftVC.parentViewController {
                print("leftVC is using")
            } else {
                print("set leftVC = nil")
                leftViewController = nil
            }
        }

        if let rightVC = rightViewController {
            if let _ = rightVC.parentViewController {
                print("rightVC is using")
            } else {
                print("set rightVC = nil")
                rightViewController = nil
            }
        }
    }


    @IBAction func onSegmentValueChanged(sender: UISegmentedControl) {
        UIView.beginAnimations("xxx", context: nil)
        UIView.setAnimationDuration(0.4)
        UIView.setAnimationCurve(.EaseInOut)

        switch sender.selectedSegmentIndex {
        case 0:
            UIView.setAnimationTransition(.FlipFromRight, forView: self.containerView, cache: true)

            if let leftVC = leftViewController {
                switchViewController(from: rightViewController, to: leftVC)
            } else {
                if let sb = storyboard {
                    leftViewController = sb.instantiateViewControllerWithIdentifier("leftViewController") as! LeftViewController
                    switchViewController(from: rightViewController, to: leftViewController)
                } else {
                    print("storyboard is nil")
                }
            }
        default:
            UIView.setAnimationTransition(.FlipFromLeft, forView: self.containerView, cache: true)

            if let rightVC = rightViewController {
                switchViewController(from: leftViewController, to: rightVC)
            } else {
                if let sb = storyboard {
                    rightViewController = sb.instantiateViewControllerWithIdentifier("rightViewController") as! RightViewController
                    switchViewController(from: leftViewController, to: rightViewController)
                } else {
                    print("storyboard is nil")
                }
            }
        }

        UIView.commitAnimations()
    }
}