28

I am in kind of situation that I need to start with a tab based application and in that I need a split view for one or more tabs. But it seems that split view controller object can not be added to the tabbarController. (Although tabbar object can be added to the splitviewcontroller).

The problem can be seen otherways: I have a full screen in the left part I have a table view when any row is selected in the table a popover should come out pointing that row. Now when any row in the popover is selected the rows in this popover comes to the left under the selected row (only this row would be visible) and another popover comes out from the selected row. (Breadcrumb navigation type)

I think I am clear in what I explained. So guys any ideas or work arounds?

Please let me know if I am not clear in my question.

Thanks,

Madhup

Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
Madhup Singh Yadav
  • 8,110
  • 7
  • 51
  • 84

9 Answers9

19

Using the interface builder, create a split view controller and a tab bar controller and link them to your outlets:

@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
@property (nonatomic, retain) IBOutlet UISplitViewController *splitViewController;

In your app delegate didFinishLaunchingWithOption, assign your split view controller to the tab bar controller:

splitViewController.tabBarItem = [[[UITabBarItem alloc] initWithTitle:@"Title" image:nil tag:0] autorelease];
NSArray *controllers = [NSArray arrayWithObjects:splitViewController,  /* other controllers go here */ nil];
tabBarController.viewControllers = controllers;
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];

This will create a tab bar controller (with only 1 tab in this case), which is displayed correctly in all orientations.

g_fred
  • 5,878
  • 3
  • 28
  • 36
  • 4
    Is doing this really OK? I can see that for example the Yelp iPad app puts a tab bar at the root with multiple split VCs inside it, but there is this paragraph in the official view controller programming guide: "A split view controller must always be the root of any interface you create. In other words, you must always install the view from a UISplitViewController object as the root view of your application’s window. The panes of your split-view interface may then contain navigation controllers, tab bar controllers, or any other type of view controller you need to implement your interface." – glenc Feb 19 '11 at 22:27
  • I'm sure if it's written by Apple then there is a reason. All programmatic tricks won't work in some special situations. – slatvick Mar 13 '11 at 14:19
  • This isn't a flawless implementation. As Greg points out below, any splitviews in tabs that aren't currently visible won't receive the orientation message, and could be displaying incorrectly when you finally visit them, forcing you to re-rotate to get it oriented properly. His subclass, as long as the app store keeps accepting it, is the best solution for now without going to fully custom solutions. – Bob Spryn Jul 14 '11 at 01:37
9

I've written up a subclass for the UISplitViewController that will listen for changes to device orientation and orient itself accordingly. With this class, I can now place split views within a UITabBarController and each split view will behave correctly upon rotation, even if it's not the frontmost tab. I've successfully deployed this in TexLege and it was approved for use in the App Store, but your mileage may vary. Please see the repository at Github.

Feel free to fork and modify it, and I'm always interested in hearing comments (or complaints) about it. https://github.com/grgcombs/IntelligentSplitViewController

Greg Combs
  • 4,252
  • 3
  • 33
  • 47
  • I'm still struggling with this issue, I think the only way to go is by creating a custom UISplitViewController-like implementation from scratch. That said, I just took a look at your code and it doesn't looks very AppStore-safe, having references to private instances of the UIBarButtonItem and UIPopOverController that the SplitViewController handles... – boliva Nov 16 '10 at 18:01
  • Yep that's my main issue too. That being said, this was, to me, the cleanest, most reliable approach until Apple addresses it themselves (unlikely). But, it's been approved by four different reviewers and passed. I'm not making an explicit call, which would be more problematic. Maybe it'll help folks or spark further development of alternatives. I'm open. – Greg Combs Dec 10 '10 at 22:58
  • This solution does not actually work, if you log the frame size of the split view controller you get the following results (S for showing and NS for not showing) SPortaitWidth:768 / SLandscapeWidth:1024 versus NSPortraitWidth:768 / NSLanscapeWidth: 768. No change is made in its frame size... – Matt Dec 17 '10 at 15:33
  • Not sure about your implementation but it's working pretty well for folks now. – Greg Combs Jul 12 '11 at 03:57
  • 1
    Any reports of this being the cause of an app denial? Has everyone had success? If so, I think its a great solution until Apple updates its behavior. – Bob Spryn Jul 14 '11 at 01:20
  • I've had zero news about denial and lots of news of success. I figure there's two potential reasons why Apple *could* deny it ... 1) docs say that a split view should be the root (topmost) view and 2) to get to the popover button quickly, I call for it "directly". At least with #2 though, if you get into trouble with it, I provide an alternative that just walks up the view hierarchy to get to the button more "legally". – Greg Combs Jul 14 '11 at 14:56
  • In the IntelligentSplitViewController example, why does the master view not show in landscape mode? Also, there is no navigation button to popover the master view. – Gamma-Point Jun 06 '12 at 06:02
7

I made a sample application. and found we can do it programmatically like:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

        NSMutableArray *array = [NSMutableArray array];

        NSMutableArray *tabArray = [NSMutableArray array]; 

        UISplitViewController *splitViewConntroller = [[UISplitViewController alloc] init];

        MainViewController *viewCont = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];
        [array addObject:viewCont];
        [viewCont release];

        viewCont = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];
        [array addObject:viewCont];
        [viewCont release];




        [splitViewConntroller setViewControllers:array];

        [tabArray addObject:splitViewConntroller];

        [splitViewConntroller release];

        array = [NSMutableArray array];

        splitViewConntroller = [[UISplitViewController alloc] init];

        viewCont = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];
        [array addObject:viewCont];
        [viewCont release];

        viewCont = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];
        [array addObject:viewCont];
        [viewCont release];

        [splitViewConntroller setViewControllers:array];

        [tabArray addObject:splitViewConntroller];

        [splitViewConntroller release];

        // Add the tab bar controller's current view as a subview of the window
        [tabBarController setViewControllers:tabArray];

        [window addSubview:tabBarController.view];
        [window makeKeyAndVisible];

        return YES;
    }

Hope this helps.

Madhup Singh Yadav
  • 8,110
  • 7
  • 51
  • 84
  • 3
    You would be very sorry if you'd attempt to do this in iOS 4.2 or 4.3 and completely disastrous in iOS3.2 It works, but it doesn't resize properly when changing orientation or starting your app in the landscape mode. – bioffe Mar 07 '11 at 18:29
  • I've found a simple way to propagate all rotation events from the TabBarController to all child SplitViewControllers. See my answer. – Brody Robertson Mar 28 '13 at 15:36
2

I created a UITabBarController subclass which properly propagates the rotation messages to all UISplitViewControllers it contains. This maintains the correct internal state of the UISplitViewControllers. However, one of the SplitViewController delegate methods is not called if the SplitViewController is not visible, so I account for this in the detail view controller viewWillAppear method. I've confirmed this works in iOS5.0 - iOS6.1

OSTabBarController.m

#import "OSTabBarController.h"

@implementation OSTabBarController

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    for(UIViewController *targetController in self.viewControllers){
        if(targetController != self.selectedViewController && [targetController isKindOfClass:[UISplitViewController class]]){
            [targetController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
        }
    }
}

-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    for(UIViewController *targetController in self.viewControllers){
        if(targetController != self.selectedViewController && [targetController isKindOfClass:[UISplitViewController class]]){
            [targetController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
        }
    }
}

@end

DetailViewController

@implementation OSDetailViewController

-(void)viewWillAppear:(BOOL)animated{
    //the splitViewController:willHideViewController:withBarButtonItem:forPopoverController: may not have been called
    if(!UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
        self.navigationItem.leftBarButtonItem = nil;
    }
}

#pragma mark - UISplitViewControllerDelegate Methods

- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
{
    [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];

}

- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    [self.navigationItem setLeftBarButtonItem:nil animated:YES];
}

@end
Brody Robertson
  • 8,506
  • 2
  • 47
  • 42
  • You do not need to duplicate your answer on every similar question. One is enough. – j0k Mar 28 '13 at 16:16
2

To let a tabbarcontroller appear as a master view for splitviewcontroller you should rewrite tabbarcontroller so that it will support or orientations(so say, using a category for the class UITabBarController)

Yan Cheng
  • 21
  • 1
2

See my post about retrofitting split view controllers to an existing tab bar interface: http://markivsblog.blogspot.com/2010/04/retrofitting-ipad-uisplitviewcontroller.html

user318668
  • 31
  • 1
1

Keep in mind that OS 3.2 does not provide proper support for a splitview as a tabbar view.

You can make it "work" but it will have bugs - the biggest is that an orientation change made on another tab's view will often not propagate to the splitview tab view properly, making the view go wacky when you go back to it (left side view takes over the screen, or the barbutton item is missing, etc.).

I've reached the conclusion that I have to create my own splitview for use in a tabBarController because of this issue.

I had heard rumors that Apple was working on a fix but it's been months now and no iPad OS updates have occurred - maybe OS 4 for the iPad will address it.

Jason
  • 1,323
  • 14
  • 19
  • Jason, can you elaborate on your approach? You're subclassing the splitviewcontroller or you're using categories? What method calls are the culprit? I've been chasing my tail trying to find the correct means of alerting the other splitviews of an orientation change. I'm not above brute force, but I'm exhausted by this point. – Greg Combs Sep 21 '10 at 19:57
  • I think I figured it out ... I catch both of the status bar orientation change notifications, then manually call the super will/did rotate if the splitview isn't the visible one. Then to take care of the popover buttons that now appear in landscape, I also manually notify the splitview delegate when we'll be transitioning from portrait to landscape and back. – Greg Combs Sep 21 '10 at 22:00
  • I wrote my own without referencing any of the built in splitviewcontroller stuff. Main gotchas for me were making sure I deal with the case of a low-mem occurring in another tab and then ensuring the the split view tab can set itself up proerply when it comes back up. Other than that the main work is in determining the "Current orientation" when you get active/shown again and adjusting the views as needed. I found that the current "Device" orientation wasn't always a good value to reference, so I grab the current orientation value from the tabbarcontroller itself. – Jason Sep 23 '10 at 18:02
  • Hi Jason, would you care to elaborate a little bit more on your approach and maybe share some code? I'm struggling with the exact same issue where the orientation changes aren't propagated to the non-visible (split) view controllers of the tab Bar. – boliva Nov 03 '10 at 17:07
  • Boliva, unfortunately no code to share, but to catch "missed" orientation changes I had to check the tabbarcontroller UIOrientation on each viewWillAppear and re-size the views as needed. – Jason Nov 04 '10 at 13:19
  • I wish I'd listen to your advice. Even in iOS 4.3 this is still a very bad idea. – bioffe Mar 07 '11 at 18:31
  • The rotation notification listeners here seem to help this issue. I think iOS 6 may have ways of passing down events from the parent (tabbarcontroller) to the child (uisplitviewcontroller), assuming you are using that as your min iOS version, but I haven't explored that. https://github.com/grgcombs/IntelligentSplitViewController/blob/master/IntelligentSplitViewController.m – Jason Oct 08 '12 at 20:58
  • I've found a simple way to propagate all rotation events from the TabBarController to all child SplitViewControllers. See my answer. – Brody Robertson Mar 28 '13 at 15:35
0

We succeeded in having a UISplitViewController inside a UITabViewController on iPad with iOS5+.

to make a long story short: it works:

  • out of the box if you accept a split also in portrait;
  • with a bit of work, if you want to have the master view hidden in portrait, and have it appear only upon tapping a button.

The trick in the second case is to use the IntelligentSplitViewController (see a few posts up, thanx Greg Combs) or similarly extend a UISplitVC, and be careful that the delegate of the subclass of the splitview controller is always a live object.

We have detailed the process on:

https://devforums.apple.com/message/763572#763572

zontar
  • 485
  • 7
  • 18
  • I've found a simple way to propagate all rotation events from the TabBarController to all child SplitViewControllers. See my answer. – Brody Robertson Mar 28 '13 at 15:37
0

You can use IB to build tabtab and modify tabs to splitviewcontroller.

-(void) makeSplitViewController {
NSMutableArray *controllers = [NSMutableArray arrayWithArray:tabBarController.viewControllers];
int index = 0;

for (UIViewController *controller in tabBarController.viewControllers) {
    if ([controller.tabBarItem.title isEqualToString:@"Stock"]) {
        stockDetailController = [[StockDetailController alloc] initWithNibName:@"StockDetailController" bundle:nil];

        stockMasterController = [[StockMasterController alloc] initWithStyle:UITableViewStylePlain]; 
        stockMasterController.navigationItem.title = date;
        stockMasterController.stockDetailController = stockDetailController;

        UINavigationController *nav = [[[UINavigationController alloc] initWithRootViewController:stockMasterController] autorelease];

        splitViewController = [[UISplitViewController alloc] init];
        splitViewController.tabBarItem = controller.tabBarItem;
        splitViewController.viewControllers = [NSArray arrayWithObjects:nav, stockDetailController, nil];
        splitViewController.delegate = stockDetailController;

        [controllers replaceObjectAtIndex:index withObject:splitViewController];
    }

    index++;
}

tabBarController.viewControllers = controllers;

}

mikezang
  • 2,291
  • 7
  • 32
  • 56