iOS SplitView Controller, Programatically Change View With Multiple Storyboards - ios

Hopefully someone can help.
I've got an app that uses the UISplitViewController however I've now had to start using multiple storyboards because i've got a large amount of views and Xcode was starting to run really slow. I've moved moved the UIViewControllers to multiple storyboards.
The Master View is built from static cells, so when the user selects the cell I normally change the view by pushing a segue.
I'm now wondering how to programmatically change the detail view of a UISplitViewController?
Thanks

Subclass UISplitViewController and set your root splitViewController to that class. Then add this method to your UISplitViewController subclass:
-(void)setDetailControllerTo:(UIViewController *)detailController withNavControllerTitle:(NSString *)title {
[detailController view]; // this line forces the viewDidLoad method to be called
if (title) {
UINavigationController *navController = [[UINavigationController alloc] init];
[navController pushViewController:detailController animated:YES];
detailController.title = title;
NSArray *viewControllers=#[self.mainController.viewControllers[0],navController];
self.mainController.viewControllers = viewControllers;
} else {
NSArray *viewControllers=#[self.mainController.viewControllers[0],detailController];
self.mainController.viewControllers = viewControllers;
}
}
To call this method do something like this from the master view controller:
FixedSplitViewController *splitController = (FixedSplitViewController*) self.splitViewController;
CurrentEventViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"CurrentEventViewController"];
// add any setup code here
[splitController setDetailControllerTo:controller withNavControllerTitle:#"Current Event"];
A lot of my projects require the splitviewcontroller to always show the master view so I use this subclass to keep the master view from hiding on portrait rotation.

Related

How to hide MasterViewController in UISplitViewController for iPad

I am working with UISplitViewController,where i need to hide
MasterViewController for 1st DetailViewController. Means,when app
launches,the MasterViewController must be hidden without clicking on
any UIButton. I tried using UISplitViewController inside
UIViewControllers.But nothing happened. After that I tried to develop overall app within UISplitViewController. Here is the code I tried to hide MasterViewController
masterViewController = [splitViewController.viewControllers objectAtIndex:0];
detailVC = [splitViewController.viewControllers objectAtIndex:1];
[master.view setFrame:CGRectMake(0, 0, 0, 0)];
detail.view.frame = splitViewController.view.bounds;
I have spent my 1 week doing this,but nothing worked.Please provide
solution.It would be great pleasure.
UPDATE:
Ok done with hiding master view controller.But when i set delegate to
detail view controller,it hides master view controller for all view
controllers. Here is my appDelegate's code where I am adding
UISplitViewController.
self.splitViewController =[[UISplitViewController alloc]init];
MasterTableViewController *masterViewController=[[MasterTableViewController alloc]initWithNibName:#"MasterTableViewController" bundle:nil];
UINavigationController *masterNavigate=[[UINavigationController alloc]initWithRootViewController:masterViewController];
DetailViewController *detailVC=[[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
UINavigationController *detailNavigate=[[UINavigationController alloc]initWithRootViewController:detailVC];
self.splitViewController.viewControllers=[NSArray arrayWithObjects:masterNavigate,detailNavigate, nil];
self.splitViewController.delegate=detailVC;
self.window.rootViewController=self.splitViewController;
Here,I set delegate of splitViewController to detailVC.In
detailViewController with Nex Mishra's code I am hiding master view
controller and when I navigate to other views from detailVC,it hides
the master view controller. I know it would be irritating,But it would
be helpful.
You could use this delegate method to hide the master VC.
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
//Add your own logic to when you want the master VC hidden.
return YES;
}
- (BOOL)splitViewController:(UISplitViewController*)svc
shouldHideViewController:(UIViewController *)vc
inOrientation:(UIInterfaceOrientation)orientation
{
// this commant is used for showing or hiding the split view controller
svc.presentsWithGesture = TRUE;
return YES;
}
// dont forget to assign the delegate like this in viewWillAppear
(self.splitViewController.delegate = self;)

Using multiple storyboards with a TabBarController

Okay, so in the process of developing my newest app, I found that my storyboard got huge, so in an effort to clean it up some, i have divided it into multiple storyboards before it gets out of hand. just for settings alone i have roughly 20 tableviewcontrollers that branch out from a root NavigationController. That navigationcontroller was a TabItem on a TabBarController, which is the application's root view controller.
I've moved the TabBar into it's own StoryBoard as the Root_Storyboard and the Navigation controller is now the initial view of the Settings_Storyboard.
Just for testing purposes, I placed a few UIViewControllers as tab items in the TabBarController (Root_Storyboard) and subclassed one and added the following code to it's viewWillAppear method. It works great, but I know that the presentViewController displays the NavigationController modally and hides the tabBar. Obviously I don't want that, how do I get it to push properly so that the TabBar remains visible?
- (void) viewWillAppear:(BOOL)animated {
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"Settings_iPhone" bundle:nil];
UIViewController *rootSettingsView = [settingsStoryboard instantiateInitialViewController];
[self.tabBarController presentViewController:rootSettingsView animated:NO completion:NULL];
}
Edit - To clarify. The above code is the subclassed method for a UIViewController (child of UITabBarController:index(1)) in the Root_iPhone.storyboard. The UINavigationController/UITableViewController that I am trying to load is found in Settings_iPhone.storyboard. Not sure how to implement the linkView suggested below in this situation.
This is quite possible and a smart move - decluttering your Storyboards presents cleaner interface files to dig through, reduced loading times in XCode, and better group editing.
I've been combing across Stack Overflow for a while and noticed everyone is resorting to Custom Segues or instantiating tab based setups programmatically. Yikes. I've hacked together a simple UIViewController subclass that you can use as a placeholder for your storyboards.
Code:
Header file:
#import <UIKit/UIKit.h>
#interface TVStoryboardViewController : UIViewController
#end
Implementation file:
#import "TVStoryboardViewController.h"
#interface TVStoryboardViewController()
#property (nonatomic, strong) UIViewController *storyboardViewController;
#end
#implementation TVStoryboardViewController
- (Class)class { return [self.storyboardViewController class]; }
- (UIViewController *)storyboardViewController
{
if(_storyboardViewController == nil)
{
UIStoryboard *storyboard = nil;
NSString *identifier = self.restorationIdentifier;
if(identifier)
{
#try {
storyboard = [UIStoryboard storyboardWithName:identifier bundle:nil];
}
#catch (NSException *exception) {
NSLog(#"Exception (%#): Unable to load the Storyboard titled '%#'.", exception, identifier);
}
}
_storyboardViewController = [storyboard instantiateInitialViewController];
}
return _storyboardViewController;
}
- (UINavigationItem *)navigationItem
{
return self.storyboardViewController.navigationItem ?: [super navigationItem];
}
- (void)loadView
{
[super loadView];
if(self.storyboardViewController && self.navigationController)
{
NSInteger index = [self.navigationController.viewControllers indexOfObject:self];
if(index != NSNotFound)
{
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[viewControllers replaceObjectAtIndex:index withObject:self.storyboardViewController];
[self.navigationController setViewControllers:viewControllers animated:NO];
}
}
}
- (UIView *)view { return self.storyboardViewController.view; }
#end
Description:
The view controller uses its Restoration Identifier to instantiate a storyboard in your project.
Once loaded, it will attempt to replace itself in its
UINavigationController's viewController array with the Storyboard's
initial view controller.
When requested, this subclass will return the UINavigationItem of the Storyboard's initial view controller. This is to ensure that navigation items loaded into UINavigationBars will correspond to the view controllers after the swap.
Usage:
To use it, assign it as the subclass of a UIViewController in your Storyboard that belongs to a UINavigationController.
Assign it a Restoration ID, and you're good to go.
Setup:
And here's how you set it up in the Storyboard:
This setup shows a tab bar controller with navigation controllers as its first tab controllers. Each navigation controller has a simple UIViewController as its root view controller (I've added UIImageViews to the placeholders to make it easy to remember what it links to). Each of them is a subclass of TVStoryboardViewController. Each has a Restoration ID set to the storyboard they should link to.
Some wins here:
It seems to work best for modal presentations where the subclass is the root view controller of a navigation controller.
The subclass doesn't push any controllers on the stack - it swaps. This means you don't have to manually hide a back button or override tab behaviour elsewhere.
If you double tap on a tab, it will take you to the Storyboard's initial view, as expected (you won't see that placeholder again).
Super simple to set up - no custom segues or setting multiple subclasses.
You can add UIImageViews and whatever you like to the placeholder view controllers to make your Storyboards clearer - they will never be shown.
Some limitations:
This subclass needs to belong to a UINavigationController somewhere in the chain.
This subclass will only instantiate the initial view controller in the Storyboard. If you want to instantiate a view controller further down the chain, you can always split your Storyboards further and reapply this subclass trick.
This approach doesn't work well when pushing view controllers.
This approach doesn't work well when used as an embedded view controller.
Message passing via segues likely won't work. This approach suits setups where sections of interface are unique, unrelated sections (presented modally or via tab bar).
This approach was hacked up to solve this UITabBarController problem, so use it as a partial solution to a bigger issue. I hope Apple improves on 'multiple storyboard' support. For the UITabBarController setup however, it should work a treat.
This is a bit late for Hawke_Pilot but it might help others.
From iOS 9.0 onwards you can create a Relationship Segue to another storyboard. This means that Tab Bar View Controllers can link to View Controllers on another storyboard without some of the mind-bending tricks seen in other answers here. :-)
However, this alone doesn't help because the recipient in the other storyboard doesn't know it's being linked to a Tab Bar View Controller and won't display the Tab Bar for editing. All you need to do once you point the Storyboard Reference to the required View Controller is select the Storyboard Reference and choose Editor->Embed In->Navigation Controller. This means that the Nav Controller knows it's linked to a Tab Bar View Controller because it's on the same storyboard and will display the Tab Bar at the bottom and allow editing of the button image and title. No code required.
Admittedly, this may not suit everyone but may work for the OP.
Not sure if your question is answered, and for others looking for a solution to this problem, try this method.
Create the Tab Bar Controller with Navigation Controllers in one storyboard file. And add an empty view controller (I named it RedirectViewController) as shown in the picture.
The child view controller (let's call it SettingsViewController for your case) is located in Settings_iPhone.storyboard.
In RedirectViewController.m, code this:
- (void)viewWillAppear:(BOOL)animated
{
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"Settings_iPhone" bundle:nil];
UIViewController *rootSettingsView = [settingsStoryboard instantiateInitialViewController];
[self.navigationController pushViewController:rootSettingsView animated:NO completion:nil];
}
SettingsViewController will be pushed into view instantly when Settings tab is touched.
The solution is not complete yet! You will see "< Back" as the left navigationItem on SettingsViewController. Use the following line in its viewDidLoad method:
self.navigationItem.hidesBackButton = YES;
Also, to prevent the same tab bar item from being tap and causes a jump back to the blank rootViewController, the destination view controllers will need to implement UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
return viewController != tabBarController.selectedViewController;
}
It works for me.
Add Following code to your LinkViewController
-(void) awakeFromNib{
[super awakeFromNib];
///…your custom code here ..
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:self.storyBoardName bundle:nil];
UIViewController * scene = nil;
// Creates the linked scene.
if ([self.sceneIdentifier length] == 0)
scene = [storyboard instantiateInitialViewController];
else
scene = [storyboard instantiateViewControllerWithIdentifier:self.sceneIdentifier];
if (self.tabBarController)
scene.tabBarItem = self.tabBarItem;
}
Here is the screenShot for LinkViewController .
LinkViewController is just a placeholder where new viewController would be placed. Here is the sample code which I used for my app.
RBStoryboardLink . Its working great for me. Let me know if it is helpful for you.

How to structure ViewControllers using Storyboard for SplitView iPad app?

I'm trying to build the interface for an app that looks like this:
I want to use the Storyboard to create the views, but I'm having trouble figuring out how now. I've dropped a SplitViewController that is hooked up to 2 navigation controllers: MasterNavigationController, and DetailNavigationController (Master is the left side menu and Detail the right side).
I was starting to build all my ViewControllers in storyboard. Then build NSArrays to hold the various ViewController stacks needed for each of the Master's menu items. So when a user taps on a menu item, I'd load the corresponding ViewController stack into the DetailNavigationController using this method:
- (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated
But now I'm thinking, I should create a separate DetailNavigationController for each of the menu items. For example:
RecentOrdersNavigationController
CustomersNavigationController
ItemsNavigationController
...
Then when a user taps a menu item, the entire DetailNavigationController changes to the appropriate one.
How should I be structuring the the interface while using storyboard?
Use a separate viewController for each detail view. You want your code to be separate since it can get confusing fast with all the different functionality for each view. From there, you can easily swap out the the detail.
Subclass UISplitViewController and set your root splitViewController to that class. Then add this method to your UISplitViewController subclass:
-(void)setDetailControllerTo:(UIViewController *)detailController withNavControllerTitle:(NSString *)title {
[detailController view]; // this line forces the viewDidLoad method to be called
if (title) {
UINavigationController *navController = [[UINavigationController alloc] init];
[navController pushViewController:detailController animated:YES];
detailController.title = title;
NSArray *viewControllers=#[self.mainController.viewControllers[0],navController];
self.mainController.viewControllers = viewControllers;
} else {
NSArray *viewControllers=#[self.mainController.viewControllers[0],detailController];
self.mainController.viewControllers = viewControllers;
}
}
To call this method do something like this from the master view controller in the tableView:didSelectRowAtIndexPath: method
FixedSplitViewController *splitController = (FixedSplitViewController*) self.splitViewController;
CurrentEventViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"CurrentEventViewController"];
// add any setup code here
[splitController setDetailControllerTo:controller withNavControllerTitle:#"Current Event"];
If you wish to keep the master view visible in portrait rotation, add this method to the SplitViewController subclass:
-(BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation {
return NO;
}
A lot of my projects require the splitviewcontroller to always show the master view so I use this subclass to keep the master view from hiding on portrait rotation.

PushViewController doesn't do anything

This is the first time I'm trying to implement navigation from a tableView cell to another tableView using UINavigationController and it doesn't work for me.
I'm NOT using nib file and I have a simple tableView that I present it in a modal dialog in my app, it works fine, now I added the disclosureInidcator to one of it's cell, to make the user enable to choose from a fixed number of options available from another list(tableView). For this purpose I have another class that makes the second tableView. the problem is now navigation from the cell(contains disclosure icon)in first tableview to second tableView doesn't do anything, no error, no nothing. I guess the way I setup the navigation controller would be wrong, the code doesn't fall in delegate, or datasource of the second class at all.
in First TableView in method : didSelectRowAtIndexPath I tried to catch that row, then call the second tableView like this:
mySecondViewController *secondVC = [[[mySecondViewController alloc] initWithStyle:UITableViewStyleGrouped ] autorelease];
UINavigationController *navCont = [[UINavigationController alloc] initWithRootViewController: self];//not sure the first controller should act as the root controller?
[navCont pushViewController:secondVC animated:YES]; //it does nothing, no error,...
the second tableViewcontroller class contains all delegate and datasource methods, and initialization method:
- (id)initWithStyle:(UITableViewStyle)style
{
if ((self = [super initWithStyle:style])) {
}
return self;
}
and declared in interface as:
#interface stockOptionViewController : UITableViewController {
}
I tried to play with viewDidLoad, but didn't help.
Please help me cause I have no clue and all sample codes found is based on using nib files.
Thank,
Kam
Your navigation controller should be the root view controller of the app delegate's window, and the first view controller should be the root view controller of the navigation controller, then you can push new controllers onto it.
Please see the documentation for UINavigationController.
At the moment, you are creating a navigation controller but not putting it anywhere, so asking it to push new view controllers is a little pointless. You have the right code, just not in the right order.
You can present view control modally without nav controller
mySecondViewController *secondVC = [[[mySecondViewController alloc] initWithStyle:UITableViewStyleGrouped ] autorelease];
[self presentModalViewController:secondVC animated:YES];
UINavigationController should be the root view controller. In the current code, navCont is not on the view stack, so it won't work. Instead of pushing myFirstViewController in your appDelegate, push the UINavigationController to the stack and add myFirstViewController as its root view controller.

TTNavigator not pushing onto navigation stack

I am using the TTTableView class from the Three20 framework to create table view cells with styled content, including HTML with URLs. The cells look and work almost fine. The URL are picked up, and tapping on one of the fire the appropriate delegate method. However, the URL is open in a TTWebController the TTWebController does not have a back arrow to pop the view of the navigation stack.
Heres my code:
TTTableStyledTextItem *messageItem = [TTTableStyledTextItem itemWithText:[TTStyledText textFromXHTML:message lineBreaks:YES URLs:YES]];
messageItem.delegate = self;
TTListDataSource *dataSource = [[TTListDataSource alloc] init];
[dataSource.items addObject:messageItem];
TTNavigator* navigator = [TTNavigator navigator];
navigator.delegate = self;
navigator.window = [UIApplication sharedApplication].delegate.window;
TTURLMap* map = navigator.URLMap;
[map from:#"*" toViewController:[TTWebController class]];
self.tableView.dataSource = dataSource;
self.tableView.delegate = self;
The URLs are highlighted in the cells and tapping one fires this method:
- (BOOL)navigator: (TTBaseNavigator *)navigator shouldOpenURL:(NSURL *) URL {
return YES;
}
The TTWebController does not seem to be pushed onto the navigation stack, it is just "shown" without a back arrow. Any thoughts?
Update with my solution
After playing around some more I think the problem is is that I am trying to use the Three20 URL navigation method to push a new view controller while at the same time using a regular iOS UINavigationController. The point at which the TTWebcontroller is being shown is the first view controller on the Three20 navigation stack, and as such is the root view controller and so does not have any notion of "going back" to a previous view. 
Here is my work around:
- (BOOL)navigator: (TTBaseNavigator *)navigator shouldOpenURL:(NSURL *) URL {
// setup a webcontroller with the URL and push it
TTWebController *webController = [[TTWebController alloc] init];
[webController openURL:URL];
[self.navigationController pushViewController:webController animated:YES];
[webController release];
// dont navigate with TTBaseNavigator
// this does not use the regular UINavigation stack and
// ... the new view becomes rootview and has no back button
return NO;
}
Hope this helps some one.
That might happen depending on how you presented the viewController containing the tableView. It that viewController pushed onto a UINavigationController or invoked per url (in that case Three20 would automatically create a navigationController for you).
You need to make sure, that a navigationController onto with the WebController can be pushed exists.

Resources