How to share one storyboard scene between multiple UIViewControllers - ios

I'm completely re-formulating this question having learned that I was originally off track but that having me no closer to solving the problem. With reference to this image...
I am wanting to either create or manipulate the segue (highlighted in yellow) in code such that the Master view is any one of a number of subclasses of MFMasterViewController (highlighted in red).
When doing this using Nibs I could create a Nib, SharedNib.xib & set the class as MFMasterViewController, then create my subclasses, say MFMasterViewControllerSubclassA, MFMasterViewControllerSubclassB etc. & then instantiate whichever subclass I wanted using...
MFMasterViewControllerSubclassA *controller = [[MFMasterViewControllerSubclassA alloc] initWithNibName:#"SharedNib" bundle:nil];
or...
MFMasterViewControllerSubclassB *controller = [[MFMasterViewControllerSubclassB alloc] initWithNibName:#"SharedNib" bundle:nil];
etc.
Any clues as to how I can get this right using storyboards?
In my case the reason for wanting to do this is that all my subclasses are the same tableview & data but sorted differently & having some difference in what's written to the detail text of the cels. I suspect that it is a not uncommon pattern.
Cheers & TIA,
Pedro :)

It's not a direct answer but this is how I would accomplish what you want based on your explanation of the reason.
Basically you need to separate the UITableViewDataSource (and maybe the delegate too) from the MFMasterViewController so when the segue is executed you can set the correct dataSource and delegate in the view controller.
So in the Navigation Controller you need to implement the prepareForSegue:sender: method. This is where you can customize the segue before it is executed:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// you can set the segue identifier using Interface Builder
// also it is a good thing to make sure which segue you're using
if (([segue identifier] isEqualToString:#"TheId"]) {
id<UITableViewDataSource> dataSource = [[TableViewDataSourceImplementationA alloc] init];
[[[segue destinationViewController] tableView] setDataSource:dataSource];
}
}
This way you can get the customization you want without the need to create subclasses of your view controller.
And if you have access to WWDC videos, check the session #407 Adopting Storyboards in Your App.

For anyone stumbling upon this question, you should also consider more generally using a "Strategy" pattern as an alternative to subclassing your controller. The accepted answer is a form of that, where the strategy implemented comes from whatever the data source/delegate is, and can be switched out at runtime. Another example of this is https://stackoverflow.com/a/17381927/954643

If your .m file is not associated with any storyboard, wouldn't self.storyboard be Nil?
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:
#"MainStoryboard" bundle:[NSBundle mainBundle]];
ViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"HauptMenu"];
Make sure to change the storyboardWithName: to whatever your storyboard is named.
NSString * storyBoardName;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
storyBoardName = #"MainStoryboard_iPad";
} else {
storyBoardName = #"MainStoryboard_iPhone";
}
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:
storyBoardName bundle:[NSBundle mainBundle]];
ViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"HauptMenu"];

I believe i finally found the answer. We want to use storyboard ViewController with another class name. There are many workarounds like using delegates but i think this is the best one. I already answered it in another topic. Hope it helps!
https://stackoverflow.com/a/32103618/1943053

Related

Best way to present a ViewController from many different ViewController

I have n number of different ViewController's, so I want to present a PresentViewController from any other ViewControllers in Objective-C
How I am doing Currently:
So currently i am doing like this import "PresentViewController.h" in all ViewControllers then calling following code to present it:
NSString * storyboardName = #"MainStoryboard";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
PresentViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"IDENTIFIER_OF_YOUR_VIEWCONTROLLER"];
[self presentViewController:vc animated:YES completion:nil];
But by this way I have to import "PresentViewController.h" file into all other ViewControllers which seems very irritating job.
What I am looking for:
Is there any other way to present PresentViewController from a particular place and it can be presented whenever it needed from any ViewControllers?
If that is really all the code you've got, you are not doing anything with PresentViewController unique to it, so just don't mention it by name in your code:
UIViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"IDENTIFIER_OF_YOUR_VIEWCONTROLLER"];
[self presentViewController:vc animated:YES completion:nil];
If you don't mention it in your code, you don't need to import it.
But obviously if you do something with PresentViewController, like call a method of it, then things are more complicated. You could cast to id, in which case you can call any method, provided the compiler knows about it (also known as an informal protocol). But in general it would be better just to bite the bullet and do the import.
The problem isn’t that you have to import that header all over the place, but rather that:
you’re repeating that code all over the place;
you’re exposing internal implementation details about PresentViewController (such as its storyboard name and storyboard identifier) to other classes; and
if you ever want to change this to include some additional parameter, you’re going to have to manually hunt through your code for where changes are necessary rather than just changing some public interface that the compiler will then be able to warn you of issues.
Personally, I’d define an extension to UIViewController:
// UIViewController+Foo.h
#interface UIViewController(Foo)
- (void)presentFoo;
- (void)presentFooWithCompletion:(void (^)(void))completion;
#end
And
// UIViewController+Foo.m
#implementation UIViewController(Foo)
- (void)presentFoo {
[self presentFooWithCompletion:nil];
}
- (void)presentFooWithCompletion:(void (^)(void))completion {
NSString *storyboardName = #"MainStoryboard";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"IDENTIFIER_OF_YOUR_VIEWCONTROLLER"];
[self presentViewController:vc animated:YES completion:completion];
}
#end
Then any view controller that needs to present this scene can have
#import “UIViewController+Foo.h”
And
[self presentFoo];
Or if you don’t want it as an extension to UIViewController, you could define these as class methods (using the + instead of -) of your PresentViewController. But the idea is the same: Define some simple interface that all your view controllers can call without getting lost in the implementation details of PresentViewController.
Now, obviously, I have no idea what this view controller does, so I used Foo as a placeholder. You’d obviously replace that with whatever the functional purpose of this view controller is. E.g. if it was to present a “network error” message, then I’d call the methods and filenames with presentNetworkError, UIViewController+NetworkError.h, etc. But hopefully this illustrates the idea.

Programatically return to previous ViewController in different storyboard

I tried many answers here but none of them had the same exact scenario.
I'm trying to navigate to a UIViewController that's within a separated storyboard in a different bundle, so far I was able to navigate to it but am unable to return to the previous UIViewController. The method that invokes the external view controller (TabBarController) is implemented as follows:
+(void) launchExternalUI: (UIViewController *) previousViewController {
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle: [self frameworkBundle]];
TabBarController *vc = (TabBarController*) [sb instantiateInitialViewController];
[previousViewController.navigationController pushViewController:vc animated:YES];
[vc release];
}
Now the method within TabBarController that should return to the previous view controller:
- (void) navigateToPreviousViewController: (UIGestureRecognizer *)gesture {
[self.navigationController popViewControllerAnimated:YES];
}
In TabBarController, if I print all the viewcontrollers within self.navigationController, all I see is the TabBarController, shouldn't I see the previous view controller that pushed this on launchExternalUI ? The [self.navigationController popViewControllerAnimated:YES]; has no effect at all. I'm a bit lost on this.
It's also important to notice that previousViewController is defined in a local storyboard and TabBarController is implemented in a different .framework, would that cause the issue?
Thanks in advance for all the help!
**Edit: The navigation flow I need is storyboard1:VC1->storyboard2:VC2->storyboard1:VC1, I can get storyboard1:VC1->storyboard2:VC2 part to work but not storyboard2:VC2->storyboard1:VC1
I often split projects up into various Storyboards, and have created a dynamic view controller that handles the task of loading the appropriate controller from a secondary storyboard, whilst maintaining the navigation tree.
I've created a sample project and uploaded to github as it's easier than explaining all the steps here. The key part to note is the User Defined Runtime Attributes for each of the DynamicStoryboardViewControllers in the Main.stoyboard. Note also that each of the secondary storyboards need the "is initial View Controller" checked for one of your viewControllers. Not included in the example is loading a specific scene from a storyboard. This is no more than adding the "sceneName" dynamic runtime attribute much in the same way as the storyboardName attribute is added.
it's a quick sample so a little rough, but you'll get the idea of how it all works. Feel free to ask questions if you get stuck.
Cheers!
EDIT:
It dawned on me that perhaps you don't have a navigationController in the view hierarchy (as i do in my sample). And in any event, seemingly, you won't have much control over where your tab bar is introduces. So without a navigationController the [self.navigationController popViewControllerAnimated:YES] won't work;
You should test for this and either call the popViewControllerAnimated as you do, or call dismissViewControllerAnimated ;
if(self.navigationController){
[self.navigationController popViewControllerAnimated:YES];
}else{
[self.presentingViewController dismissViewControllerAnimated:Yes completion:nil];
}
Hope this helps, if not, perhaps you can supply some sample code.
Create an unwind segue within your original view controller, and use segues instead of pushing/popping view controller views onto the view array.
To create an unwind segue you should create an unwind method in the ORIGINAL viewcontroller:
- (IBAction)unwindToOriginalViewControllerSegue:(UIStoryboardSegue*)sender {
; //TODO: anything special you need, reference via sender
}
Then, in your NEW viewcontroller, in the storyboard, drag from the controller icon to the Exit icon. Link that to the Unwind segue you named above.
UIViewController * YourViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Storyboard Identifier"];
[self.navigationController pushViewController:YourViewController animated:TRUE];

Proper Way to Open a New Storyboard through UITabBarController

We are working on splitting our main storyboard into smaller ones so that it makes source control merging easier. Any ideas on what the right approach is to load a new storyboard from a UITabBar?
Here's what we have so far in our custom subclassed UITabBarController:
UITabBarItem *cardsTabItem = [self.tabBar.items objectAtIndex:kTabBarIndexCards];
cardsTabItem.image = [[UIImage imageNamed:#"navCardsOff"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
cardsTabItem.selectedImage = [[UIImage imageNamed:#"navCardsOn"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
cardsTabItem.imageInsets = UIEdgeInsetsMake(-5, 0, 5, 0);
cardsTabItem.titlePositionAdjustment = UIOffsetMake(0, -5);
I've done the same thing before, but with a UITabBarController. In that case we had a storyboard for each of the tab buttons, even though one of the storyboards only had 1 view controller in it. I think wether you're using a UITabBarController or responding to the tab bar delegate the answer is the same. For each button clicked make the determination of which storyboard the view controller you want to load is in:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"leftButtonStoryboard" bundle:[NSBundle mainBundle]];
UIViewController *vc = [storyboard instantiateInitialViewController];
//or
UIStoryboard *otherVC = [storyboard instantiateViewControllerWithIdentifier:#"CameraViewController"];
Then you can present it, push it, or whatever.
In my case since I was using a UITabBarController this was all done during initialization of the controller for all the different buttons.
It will most likely come in handy to by default name all of the different view controller your using in your storyboard (the storyboard id), I usually name them after the viewController class so I don't have to remember what I called it.
I would also recommend that you avoid using the self.storyboard property when trying to instantiate another view controller because you might end up with a situation where a controller is shared between tabs. Being explicit with which storyboard you're loading a controller from can help with readability and avoidance of bugs.
Edit - a more concrete example:
What you need to do is set the viewControllers property of your UITabViewController, I do this in its init method. For example
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
UIViewController *one = [mainStoryboard instantiateViewControllerWithIdentifier:#"VC1"];
UIViewController *two = [mainStoryboard instantiateViewControllerWithIdentifier:#"VC2"];
self.viewControllers = #[one,two];
}
return self;
}
You can use this technique if your writing it in code itself or if you're using a storyboard. Beware that if you have other view controllers already hooked up via the storyboard you'll loose then unless you instantiate them there as well. You can also use the setViewControllers: animated: method as well.
The code for creating the custom tab bar items (the buttons at the bottom) should probably go within the individual view controllers and be assigned to its tabBarItem property. The UITabBarController will use that property to create the correctly styled button. If you don't provide the property you get the default buttons starting from 1.

Storyboard UITabBaar, initiate two different views from button

I've created a storyboard that has a UITabbarController all is working well but now I want to add some logic that determines which viewcontroller a particular tabbar button will display.
Example... if a customer has a valid subscription display viewcontroller one, if no subscription display viewcontroller two.
Is this possible using storyboards, I've looked at UITabBarDelegate and prepareForSegue but struggling to piece this together?
Are there any examples of how to do this sort of thing using StoryBoards?
Many thanks
You can set it like this:
if(hasSubscription)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
ViewController1* subsection = [storyboard instantiateViewControllerWithIdentifier:#"ViewController1"];
ViewController2* subsection1 = [storyboard instantiateViewControllerWithIdentifier:#"ViewController2"];
[(UITabBarController*)self.window.rootViewController setViewControllers:[NSArray arrayWithObjects:subsection,subsection1, nil]];
}
If you want to add rootviewController according to the subscription then above answer given by soryngod is good one.
But if you want to open viewControllers after rootviewcontroller loaded, then at tabBarButton press perform following code:-
before this code, add yours viewControllerONE and viewControllerTWO to the rootViewController by segues as shown: . And give each segue an identifier in AttributeInspector, example "one" for viewControllerONE and "two" for viewControllerTWO.
then at tabBarButton action do the following:-
if(subscription)
[self performSegueWithIdentifier:#"one" sender:self];
else
[self performSegueWithIdentifier:#"two" sender:self];

How do I change "initwithNibName" in storyboard?

I want to change below code with storyboard with Xcode 4.2.
UIViewController * example = [[ExampleViewController alloc] initWithNibName:#"ExampleViewController" bundle:nil];
Now ExampleViewController.xib file exist.
but I want to make it with storyboard.
please help me.
(I'm not good at English. Sorry)
The UIStoryboard class is your friend:
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"mystoryboard"
bundle:nil];
UIViewController* vc = [sb instantiateViewControllerWithIdentifier:#"ExampleViewController"];
If it is still in its own xib file, then you don't change anything.
If you've moved everything into a storyboard, then you wouldn't often need to do this as you'd link between view controllers using segues.
If neither of the above are true, i.e. your view controller is on the storyboard but no segue connects to it, then you want UIStoryboard's instantiateViewControllerWithIdentifier: method described in the documentation. You have to set the identifier in the storyboard for this to work.

Resources