82

I'm trying to figure out how to use the different states of a UISegmentedControl to switch views, similar to how Apple does it in the App Store when switiching between 'Top Paid' and 'Top Free'.

Anbu.Karthik
  • 77,564
  • 21
  • 153
  • 132
Mark Adams
  • 30,342
  • 11
  • 73
  • 77

9 Answers9

113

The simplest approach is to have two views that you can toggle their visibility to indicate which view has been selected. Here is some sample code on how it can be done, definitely not an optimized way to handle the views but just to demonstrate how you can use the UISegmentControl to toggle the visible view:

- (IBAction)segmentSwitch:(id)sender {
  UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
  NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;

  if (selectedSegment == 0) {
    //toggle the correct view to be visible
    [firstView setHidden:NO];
    [secondView setHidden:YES];
  }
  else{
    //toggle the correct view to be visible
    [firstView setHidden:YES];
    [secondView setHidden:NO];
  }
}


You can of course further re-factor the code to hide/show the right view.

Ronnie Liew
  • 18,000
  • 14
  • 43
  • 50
  • 4
    'definitely not an optimized way to handle the views' - why? – Adam Waite Nov 22 '12 at 21:52
  • 3
    @AdamWaite because all views have to be stored in memory permanently. If your views too complicated and/or contain many other elements it will affect overall performance. That piece of code could be refactored as well. – Stas Jul 02 '13 at 05:23
  • @Stas You are right, it's better to split the logic between multiple view controllers, each responsible for his own actions and behaviours – tf.alves Feb 18 '15 at 21:09
  • using container views can lead to to problems with the navigation bar. especially when you are using a translucent one. from my experience its not a recommendable solution – DamirDiz Jun 18 '15 at 14:25
44

In my case my views are quite complex and I cannot just change the hidden property of different views because it would take up too much memory.

I've tried several solutions and non of them worked for me, or performed erratically, specially with the titleView of the navBar not always showing the segmentedControl when pushing/popping views.

I found this blog post about the issue that explains how to do it in the proper way. Seems he had the aid of Apple engineers at WWDC'2010 to come up with this solution.

http://redartisan.com/2010/6/27/uisegmented-control-view-switching-revisited

The solution in this link is hands down the best solution I've found about the issue so far. With a little bit of adjustment it also worked fine with a tabBar at the bottom

Marc M
  • 974
  • 10
  • 9
  • Thank you for the great find. Definitely a nice and elegant solution for this methodology. – Shiun Nov 10 '10 at 19:49
  • 1
    I tried to get this working properly with the toolbar at the bottom without success, http://stackoverflow.com/questions/4748120/uisegmentedcontroller-to-switch-between-uiviewcontrollers-calendar-app-style Can you please help me out? – Erik Jan 20 '11 at 15:19
  • Is there a way to have an horizontal flip animation in between Views. Or it only works without animation ? – aneuryzm May 12 '11 at 09:54
  • Yes, this seems to be a great solution, but how do I adjust this to work with tabBarController with navigationControllers already inside? – Vladimir Stazhilov Mar 26 '12 at 12:37
  • I found a drawback using an extra `UINavigationController`. Imagine you have a `UISegmentedControl` vertically placed at the middle of a view. The views controlled by the `UISegmentedControl` are placed beneath. The problem arises when any of these *child* views perform a *segue*. You're (at least I was) not able to transition the root view (the one which contains the `UISegmentedControl`) to the destination controller, only the view placed beneath. If anyone knows a solution for this, I'd love to hear! [@crafterm](http://stackoverflow.com/users/298226/crafterm), any hints? – jweyrich Jul 18 '12 at 06:19
  • A workaround to the aforementioned is to pass the parent VC to the child VCs, then use the parent's `navigationController` to transition. With segues, you have to change the original segue: link the parent VC with the destination VC, and call the parent's `performSegueWithIdentifier`. Ugly, but works. A better solution I believe would be [Implementing a Container View Controller](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW81). Haven't tested yet, but will keep you posted. – jweyrich Jul 19 '12 at 20:47
  • 2
    Fortunately, _[Implementing a Container View Controller](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW81)_ worked flawlessly! Even segues work as expected. – jweyrich Jul 23 '12 at 19:09
  • Good solution, but little confusing – Vladimir Stazhilov Oct 01 '12 at 10:28
  • i know this is very old but do you guys happen to have the solution for this? the link linked in the answer doesn't work anymore :( – sudoExclaimationExclaimation Oct 07 '15 at 04:47
17

Or if its a table, you can reload the table and in cellForRowAtIndex, populate the table from different data sources based on the segment option selected.

lostInTransit
  • 68,087
  • 58
  • 193
  • 270
7

One idea is to have the view with the segmented controls have a container view that you fill with the different subviews (add as a sole subview of the container view when the segments are toggled). You can even have separate view controllers for those subviews, though you have to forward on important methods like "viewWillAppear" and "viewWillDisappear" if you need them (and they will have to be told what navigation controller they are under).

Generally that works pretty well because you can lay out the main view with container in IB, and the subviews will fill whatever space the container lets them have (make sure your autoresize masks are set up properly).

Kendall Helmstetter Gelner
  • 73,251
  • 26
  • 123
  • 148
3

Try using SNFSegmentedViewController, an open-source component that does exactly what you're looking for with a setup like UITabBarController.

sethfri
  • 1,202
  • 1
  • 14
  • 29
2

Assign .H in

 UISegmentedControl *lblSegChange;

- (IBAction)segValChange:(UISegmentedControl *) sender

Declare .M

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

 if(sender.selectedSegmentIndex==0)
 {
  viewcontroller1 *View=[[viewcontroller alloc]init];
  [self.navigationController pushViewController:view animated:YES];
 }
 else 
 {
  viewcontroller2 *View2=[[viewcontroller2 alloc]init];
  [self.navigationController pushViewController:view2 animated:YES];
 }
} 
Anbu.Karthik
  • 77,564
  • 21
  • 153
  • 132
2

From the answer of @Ronnie Liew, I create this:

//
//  ViewController.m
//  ResearchSegmentedView
//
//  Created by Ta Quoc Viet on 5/1/14.
//  Copyright (c) 2014 Ta Quoc Viet. All rights reserved.
//
#define SIZE_OF_SEGMENT 56
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController
@synthesize theSegmentControl;
UIView *firstView;
UIView *secondView;
CGRect leftRect;
CGRect centerRect;
CGRect rightRect;
- (void)viewDidLoad
{
    [super viewDidLoad];
    leftRect = CGRectMake(-self.view.frame.size.width, SIZE_OF_SEGMENT, self.view.frame.size.width, self.view.frame.size.height-SIZE_OF_SEGMENT);
    centerRect = CGRectMake(0, SIZE_OF_SEGMENT, self.view.frame.size.width, self.view.frame.size.height-SIZE_OF_SEGMENT);
    rightRect = CGRectMake(self.view.frame.size.width, SIZE_OF_SEGMENT, self.view.frame.size.width, self.view.frame.size.height-SIZE_OF_SEGMENT);

    firstView = [[UIView alloc] initWithFrame:centerRect];
    [firstView setBackgroundColor:[UIColor orangeColor]];
    secondView = [[UIView alloc] initWithFrame:rightRect];
    [secondView setBackgroundColor:[UIColor greenColor]];
    [self.view addSubview:firstView];
    [self.view addSubview:secondView];

}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)segmentSwitch:(UISegmentedControl*)sender {
    NSInteger selectedSegment = sender.selectedSegmentIndex;
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.2];
    if (selectedSegment == 0) {
        //toggle the correct view to be visible
        firstView.frame = centerRect;
        secondView.frame = rightRect;
    }
    else{
        //toggle the correct view to be visible
        firstView.frame = leftRect;
        secondView.frame = centerRect;
    }
    [UIView commitAnimations];
}
@end
Envil
  • 2,584
  • 1
  • 24
  • 38
2

Swift version:

The parent view controller is responsible for setting the size and position of the view of each child view controller. The view of the child view controller becomes part of the parent view controller's view hierarchy.

Define lazy properties:

private lazy var summaryViewController: SummaryViewController = {
   // Load Storyboard
   let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

   // Instantiate View Controller
   var viewController = storyboard.instantiateViewController(withIdentifier: "SummaryViewController") as! SummaryViewController

   // Add View Controller as Child View Controller
   self.add(asChildViewController: viewController)

   return viewController
}()

private lazy var sessionsViewController: SessionsViewController = {
    // Load Storyboard
    let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

    // Instantiate View Controller
    var viewController = storyboard.instantiateViewController(withIdentifier: "SessionsViewController") as! SessionsViewController

    // Add View Controller as Child View Controller
    self.add(asChildViewController: viewController)

    return viewController
}()

Show/Hide Child View Controllers:

private func add(asChildViewController viewController: UIViewController) {
    // Add Child View Controller
    addChildViewController(viewController)

    // Add Child View as Subview
    view.addSubview(viewController.view)

    // Configure Child View
    viewController.view.frame = view.bounds
    viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

    // Notify Child View Controller
    viewController.didMove(toParentViewController: self)
}

private func remove(asChildViewController viewController: UIViewController) {
    // Notify Child View Controller
    viewController.willMove(toParentViewController: nil)

    // Remove Child View From Superview
    viewController.view.removeFromSuperview()

    // Notify Child View Controller
    viewController.removeFromParentViewController()
}

Manage SegmentedControl tapEvent

private func updateView() {
    if segmentedControl.selectedSegmentIndex == 0 {
        remove(asChildViewController: sessionsViewController)
        add(asChildViewController: summaryViewController)
    } else {
        remove(asChildViewController: summaryViewController)
        add(asChildViewController: sessionsViewController)
    }
}

And of course you are able to use within your child view controller classes:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    print("Summary View Controller Will Appear")
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    print("Summary View Controller Will Disappear")
}

Reference: https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/

SlavisaPetkovic
  • 287
  • 2
  • 10
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/19273359) – Basile Perrenoud Mar 29 '18 at 14:41
  • 2
    @BasilePerrenoud Just updated the answer with crucial and most important part of the solution. – SlavisaPetkovic Mar 29 '18 at 15:23
1

A quick Swift Version:

@IBAction func segmentControlValueChanged(_ sender: UISegmentedControl) {

    if segmentControl.selectedSegmentIndex == 0 {

        // do something
    } else {

        // do something else
    }
}
Bright Future
  • 4,961
  • 2
  • 47
  • 67