Switching Viewcontrollers with Uisegmentedcontrol in iOS5

Switching ViewControllers with UISegmentedControl in iOS5

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

Switching viewControllers with a UISegmentedControl

While I don't think this is a nice solution (I would prefer creating a separate view controller, with the segmented control, and implement the switching logic there), the answer is yes: you can call this piece of code from your table view controller, adding the navigation controller's view as its subview, like so:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

NSArray * viewControllers = [self segmentViewControllers];

UINavigationController * navigationController = [[UINavigationController alloc] init];
self.segmentsController = [[SegmentsController alloc] initWithNavigationController:navigationController viewControllers:viewControllers];

self.segmentedControl = [[UISegmentedControl alloc] initWithItems:[viewControllers arrayByPerformingSelector:@selector(title)]];
self.segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;

[self.segmentedControl addTarget:self.segmentsController
action:@selector(indexDidChangeForSegmentedControl:)
forControlEvents:UIControlEventValueChanged];
[self firstUserExperience];
[self.view addSubview:navigationController.view];

}

I have downloaded the example you referred to (quite outdated by the way), and included it in a sample project. so you can check it out. All you need to do is add a new item, then select a row to invoke the relevant code piece.

Check the code here

UPDATE:

I have added a second example which uses a separate view controller, check it out if you want: Link

Switching between UIViewControllers with UISegmentedControl

So even though this isn't using separate TableViewControllers, you can use different custom UIViews that are hidden by default and become visible when you select it's corresponding button. This will unfortunately make it so you have all three view's logic in the same VC.

To get around this, you can try setting up some delegates and mimicking the TableViewController logic separation by sending out the didSelectTableAtIndexPath, UIGesture touches, etc into classes outside the ViewController to help keep your code cleaner.

Changing view controller when Segmented Control changes

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.

UIKit controller/view to switch between views?

Create a container viewController and include tis logics:

@property (nonatomic, strong) UISegmentedControl *segmentedControl;
@property (nonatomic, strong) NSMutableArray *viewControllers;
@property (nonatomic, weak) UIView *containerView;
@property (nonatomic) NSInteger currentSelectedIndex;

_segmentedControl = [[UISegmentedControl alloc] initWithItems:nil];
[_segmentedControl addTarget:self action:@selector(changeViewController:) forControlEvents:UIControlEventValueChanged];

- (void)changeViewController:(UISegmentedControl *)segmentedControl
{
UIViewController *oldViewController = self.viewControllers[self.currentSelectedIndex];
UIViewController *newViewController = self.viewControllers[segmentedControl.selectedSegmentIndex];

[self willTransitionToViewController:newViewController];
[self transitionFromViewController:oldViewController
toViewController:newViewController
duration:0
options:UIViewAnimationOptionTransitionNone
animations:nil
completion:^(BOOL finished) {
if (finished)
[self didTransitionToViewController:newViewController];
}];
}

- (void)showFirstViewController
{
UIViewController *firstViewController = [self.viewControllers firstObject];

// set required the frame
firstViewController.view.frame = self.containerView.bounds;
firstViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;

[self.containerView addSubview:firstViewController.view];

[self willTransitionToViewController:firstViewController];
[self didTransitionToViewController:firstViewController];
}

- (void)willTransitionToViewController:(UIViewController *)viewController
{
if (self.currentSelectedIndex != UISegmentedControlNoSegment)
{

UIViewController *oldViewController = self.viewControllers[self.currentSelectedIndex];
[oldViewController willMoveToParentViewController:nil];
}

viewController.containerView.frame = self.ContainerView.bounds;
viewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
}

- (void)didTransitionToViewController:(UIViewController *)viewController
{
[viewController didMoveToParentViewController:self];

self.segmentedControl.selectedSegmentIndex = [self.viewControllers indexOfObject:viewController];
self.currentSelectedIndex = [self.viewControllers indexOfObject:viewController];

}

Setting default segment in UISegmentedControl

The segmented control does not exist until the OptionsViewController is loaded which is why you cannot set the selected segment in the prepareForSegue method of you ViewController. Review the UISegmentedControl Class Reference reveals that there is no way to set a default state:

"The default value is UISegmentedControlNoSegment (no segment selected) until the user touches a segment. Set this property to -1 to turn off the current selection. UISegmentedControl ignores this property when the control is in momentary mode. When the user touches a segment to change the selection, the control event UIControlEventValueChanged is generated; if the segmented control is set up to respond to this control event, it sends a action message to its target."

Setting the selectedSegmentIndex in the viewDidLoad method of your OptionsViewController is the correct way to set a default value the view controller.

UISegmentedControl in a PopoverController with multiple view controllers

If its a different viewController for each one of the segments on the segmentBar, you'll have to use a container viewController that adds the views of each of the viewController as a subview on itself or sets its view to that of the viewController's view. For example:

UIViewController* containerController = [[[UIViewController alloc] init] autorelease];

//Inside the viewDidLoad of the the ContainerController class, do the following:

//Initialize all three viewControllers
UIViewController* test1 = [[[UIViewController alloc] init] autorelease];
UIViewController* test1 = [[[UIViewController alloc] init] autorelease];
UIViewController* test1 = [[[UIViewController alloc] init] autorelease];

//set up the segment and add it to the container's navBar's title view.
[segmentedControl addTarget:self action:@selector(segmentValueChanged:) forControlEvents:UIControlEventValueChanged];

- (void)segmentValueChanged:(id)sender
{

//if first tab selected
[self.view removeAllSubviews];
[self.view addSubview:test1.view];

//if second tab selected
[self.view removeAllSubviews];
[self.view addSubview:test2.view];

//if third tab selected
[self.view removeAllSubviews];
[self.view addSubview:test3.view];

}

Instead of adding it as a subView, you might be able to just set self.view = test1.view. Obviously, you would use the container view to initialize the navController and put that navController inside the popover. Hope this helps!

UISegmentedControl change event not firing in iOS5

This is a change in iOS 5 in order for UISegmentedControl to be consistent with all other controls.

The idea is that the action should only fired automatically as a result of user interaction. Prior to iOS 5, UISegmentedControl's actions would be fired because of user interaction and programmatic interaction. However, initiating the change programmatically means that you can also do [myControl sendActionsForControlEvents:UIControlEventValueChanged] yourself.

However, you have to be careful with this. Say you do:

[segmentedControl setSelectedSegmentIndex:newIndex];
[segmentedControl sendActionsForControlEvents:UIControlEventValueChanged];

If you build and run this on iOS 5, it works as you expect. If you build and run this on iOS 4, you'll get your actions fired twice (once when you setSelectedSegmentIndex and again when you sendActions...).

The way around this is to do some sort of guard. This could be a runtime check to indicate that you're running on an iOS 5+ device, or could even be something more mundane, like this:

// changingIndex is a BOOL ivar
changingIndex = YES;
[segmentedControl setSelectedSegmentIndex:newIndex];
changingIndex = NO;
[segmentedControl sendActionsForControlEvents:UIControlEventValueChanged];

and then in your action method...

- (void)segmentedControlSelectedIndexChanged:(id)sender {
if (!changingIndex) {
// your action code here, guaranteed to only run as a result of the sendActions... msg
}
}

Switching to different UITableViewControllers with UISegementedControl

Working solution:

    public override void ViewDidLoad ()
{
base.ViewDidLoad ();

CreateAndEmbed (TrDetailNavType.Info);
}

partial void segmentNavigationValueChanged (MonoTouch.UIKit.UISegmentedControl sender, MonoTouch.UIKit.UIEvent e)
{
CreateAndEmbed((TrDetailNavType)sender.SelectedSegment);
}

private void CreateAndEmbed(TrDetailNavType tab)
{
if (_currentViewController != null)
{
_currentViewController.View.RemoveFromSuperview ();
_currentViewController.RemoveFromParentViewController();
}

string id;
switch (tab)
{
case TrDetailNavType.Info:
id = "TagesRapportDetailInfoTableViewController";
break;
case TrDetailNavType.Lohn:
case TrDetailNavType.Material:
case TrDetailNavType.Inventar:
case TrDetailNavType.Fremdleistung:
case TrDetailNavType.Regie:
id = "TagesRapportDetailDummyViewController";
break;
}

_currentViewController = (UIViewController)Storyboard.InstantiateViewController (id);
_currentViewController.View.Frame = containerDetail.Bounds;
AddChildViewController (_currentViewController);
_currentViewController.DidMoveToParentViewController (this);
containerDetail.AddSubview (_currentViewController.View);
}


Related Topics



Leave a reply



Submit