Before the Main View Controller is called, through a delegate I'll notify Main View Controller whether to call TVC1 or TVC2 in Container View.
Question: How can I programmatically tell the Container View in Main View Controller to call TVC1 or TVC2?
TVC1 *tvc1 = [self.storyboard instantiateViewControllerWithIdentifier:#"TVC1"];
TVC2 *tvc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"TVC2"];
Where and how do I tell the Container View to load one of these controllers upon loading Main View Controller?
through delegate u can know which view will be added so u can manage it by using any bool value n in viewDidLoad you can load that view by putting one condition
in mainController:
bool isFirstView;
-(void)delegate:(bool)isFirst
{
isFirstView = isFirst;
}
// in viewDidLoad
)
(void)viewDidLoad
{
if(isFirstView)
{
TVC1 *tvc1 = [self.storyboard instantiateViewControllerWithIdentifier:#"TVC1"];
}
else
{
TVC2 *tvc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"TVC2"];
}
}
You can do this by using custom Container View Controller which will manage child view controllers.
There is a good tutorial for that:
http://sandmoose.com/post/35714028270/storyboards-with-custom-container-view-controllers
Related
I have an embedded container on a view controller, and I would like to change it's content depending on a specific condition. How should I do this, knowing that there can only be one embed segue linked to an embedded container.
I tried to put a view controller between my embedded container and my 2 possible content views, but it won't work because of custom segues (error : "Could not create a segue with class 'null'). I don't understand this error by the way, if someone could tell me more about it :)
I read about some ways to go around this problem by creating a tab view, and switching between the tabs programmatically, or by adding 2 container view and hiding the unwanted one, but these seem to be kind of hacky.
What would be the best practice to do this ? (In swift please)
Thank you for your help
There's two ways to do it, the first is to add two container views on top of each other and set the alpha to 0 for one and 1 for the other and switch the alpha values when you want to change between view controllers.
The disadvantage of this is that there will always be two view controllers instantiated.
The second way is to change the the type of segue from embed to a custom segue (this will allow you to add more than one segue in the storyboard) that loads or swaps a view controller. Here is the implementation of a segue I implemented that does this, if you can understand it you can implement it in swift.
(void)perform
//
// Used to seque between the master view controller and its immediate child view controllers and also from the homw view controller
// and all its immediate child controllers.
// At app launch then it is necessary to directly load a particular view controller - this is determined by checking that the source
// view controller has no children. At other times the seque is used to switch from one controller to another.
//
{
//
// This seque is for use when there is a container view controller where it is necessary to switch the contained view controllers (storyboards only permit one embed seque).
//
//
// embed segue segueA
// MainVC --------------------------> ContainerVC -------------------> VCA
// (has the view containing
// the containded VC)
// sequeB
// --------------------> VCB
//
//
// When the app initially launches the OS will automatically execute the embed seque and thus the ContainerVC gets the opportunity in its viewDidLoad to decide which
// VC to load at that point and then execute either segueA or sequeB. Assuming it calls sequeA then when the seque executes the source will be the ContainerVC and the
// destination with will VCA. The seque checks to see if the containerVC already has any children, if not then it knows to just add VCA as a child.
//
DDLogInfo(#"SEGUE - entered seque");
UIViewController *container = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
if([container.childViewControllers count] == 0)
{
DDLogInfo(#"SEGUE - adding intitial VC: %#", [destination description]);
// The containerVC doesn't yet any any children so just add the destination VC as a child
[container addChildViewController:destination];
destination.view.frame = container.view.frame;
[container.view addSubview:destination.view];
[destination didMoveToParentViewController:container];
}
else
{
// The containerVC already has an existing child and thus it is necessary to swap it out and replace it with the new child
UIViewController* currentChild = container.childViewControllers[0];
currentChild.view.userInteractionEnabled = NO;
PyngmeAssert([container.childViewControllers count] == 1, #"More than one child controller");
// First check to make sure the destination type is not the same as the current child
if([destination isMemberOfClass: [currentChild class]])
{
DDLogInfo(#"SEGUE: Trying to swap view controllers of the same type *****");
}
else
{
// Swap the new VC for the old VC
destination.view.frame = container.view.frame;
[currentChild willMoveToParentViewController:nil];
[container addChildViewController:destination];
[container transitionFromViewController:currentChild
toViewController:destination
duration:0.35
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
}
completion:^(BOOL finished) {
[currentChild removeFromParentViewController];
[destination didMoveToParentViewController:container];
DDLogInfo(#"SEGUE finished swapping seque");
}];
}
}
}
I'm experiencing a memory leak (the UINavigationController and its root View Controller are both being leaked) when presenting and dismissing a UINavigationController in a subview. My method of presentation of the navigation controller seems a bit non-standard, so I was hoping someone in the SO community might be able to help.
1. Presentation
The Navigation Controller is presented as follows:
-(void) presentSubNavigationControllerWithRootViewControllerIdentifier:(NSString *)rootViewControllerIdentifier inStoryboardWithName:(NSString *)storyboardName {
// grab the root view controller from a storyboard
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
UIViewController * rootViewController = [storyboard instantiateViewControllerWithIdentifier:rootViewControllerIdentifier];
// instantiate the navigation controller
UINavigationController * nc = [[UINavigationController alloc] initWithRootViewController:rootViewController];
// perform some layout configuration that should be inconsequential to memory management (right?)
[nc setNavigationBarHidden:YES];
[nc setEdgesForExtendedLayout:UIRectEdgeLeft | UIRectEdgeRight | UIRectEdgeBottom];
nc.view.frame = _navControllerParentView.bounds;
// install the navigation controller (_navControllerParentView is a persisted IBOutlet)
[_navControllerParentView addSubview:nc.view];
// strong reference for easy access
[self setSubNavigationController:nc];
}
At this point, my expectation is that the only "owner" of the navigation controller is the parent view controller (in this case, self). However, when dismissing the navigation controller as shown below, it is not deallocated (and as a result its rootViewController is also leaked, and so on down the ownership tree).
2. Dismissal
Dismissal is pretty simple, but it seems not to be sufficient for proper memory management:
-(void) dismissSubNavigationController {
// prevent an orphan view from remaining in the view hierarchy
[_subNavigationController.view removeFromSuperview];
// release our reference to the navigation controller
[self setSubNavigationController:nil];
}
Surely something else is "retaining" the navigation controller as it is not deallocated. I don't think it could possibly be the root view controller retaining it, could it?
Some research has suggested that retainCount is meaningless, but FWIW I've determined that it remains at 2 after dismissal, where I would expect it to be zero.
Is there an entirely different / better method of presenting the subNavigationController? Maybe defining the navigation controller in the storyboard would have greater benefit than simply eliminating the need for a few lines of code?
It is best practice when adding a controller's view as a subview of another controller's view, that you make that added view's controller a child view controller; that is, the controller whose view your adding it to, should implement the custom container controller api. An easy way to set this up is to use a container view in the storyboard which gives you an embedded controller automatically (you can select that controller and, in the edit menu, choose embed in Navigation controller to get the UI you're trying to make). Normally, this embedded view controller would be added right after the parent controller's view is loaded, but you can suppress that by implementing shouldPerformSegueWithIdentifier:sender:. I created a simple test app with this storyboard,
The code in ViewController to suppress the initial presentation, and the button methods to subsequently present and dismiss it is below,
#implementation ViewController
-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:#"Embed"]) { // The embed segue in IB was given this identifier. This method is not called when calling performSegueWithIdentifier:sender: in code (as in the button method below)
return NO;
}else{
return YES;
}
}
- (IBAction)showEmbed:(UIButton *)sender {
[self performSegueWithIdentifier:#"Embed" sender:self];
}
- (IBAction)dismissEmbed:(UIButton *)sender {
[[self.childViewControllers.firstObject view] removeFromSuperview];
[self.childViewControllers.firstObject willMoveToParentViewController:nil];
[self.childViewControllers.firstObject removeFromParentViewController];
}
#end
The navigation controller and any of its child view controllers are properly deallocated when the Dismiss button is touched.
The navigationController property on a UIViewController is retain/strong, which is presumably the other strong reference.
So try popping all view controllers from the navigation controller and see if that works.
I want to check if the view controller i am in is root view controller or is pushed on some navigation controller.
[self.navigationController viewControllers];
will return an array of all the viewControllers on the stack. Simply compare the first element in this array to see is the controller the root or not.
e.g.
UIViewController *vc = [[self.navigationController viewControllers] firstObject];
if([vc isEqual: <viewController to check> ])
{
// code here
}
EDIT: Add Swift
let vc = self.navigationController?.viewControllers.first
if vc == self.navigationController?.visibleViewController {
//Code Here
}
Whenever you push any view controller via navigation controller, it manages these view controllers on a stack which is maintained in Last In First Out manner. So if your current view controller is root controller than there can be only one object in the stack. You can check that stack via this code
if([self.navigationController.viewControllers count] == 1) {
//Current view controller is root controller
}
in your current View controller's viewDidLoad just check self.navigationController.viewControllers.count == 1 mean you are currently in rootview of your navigationstack. make sure you haven't present viewcontroller.
if(self.navigationController.viewControllers.count == 1)
{
//do what you want
}
With respect to #Simon answer, I'm adding my answer, to check when you're using some drawer menu, this may help you to find exact root view controller check.
- (BOOL) checkImRoot:(id)controller {
if(self.window.rootViewController) {
if(self.window.rootViewController == (UIViewController *)controller) {
return YES;
}
}
return NO;
}
In example, I'm adding this method in app delegate file, and calling it like this to check,
if([[AppDelegate shareDelegate] checkImRoot:self]) {
//Yeah, I'm a root vc
}else{
//Noo, I'm a child vc
}
My PenViewController has three labels and a Container View, which means I am using an embedded segue. The thing about embedded segue, at least per my understanding, is that they are not caused by user actions the way push segues are. But now I need my Container View to show a different child respectively when a different label is clicked. How do I pass that data to the Container View? Here is my embed segue.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"embedded_segue_to_container_vc"]) {
if ([segue.destinationViewController isKindOfClass:[BCDPenDetailContainerViewController class]]) {
BCDPenDetailContainerViewController *container = (BCDPenDetailContainerViewController *)segue.destinationViewController;
container.details=self.details;
}
}
}
The container view is just a UIView (with some IB magic thrown in), so you can create an IBOutlet to it if you need to reference it to change (or add) a child view controller. To add another child view controller, use the standard custom container controller api, and add the new controller's view to the container view,
-(IBAction)addNew:(id)sender {
UIViewController *newVC = [self.storyboard instantiateViewControllerWithIdentifier:#"NewVC"];
[self addChildViewController:newVC];
[newVC didMoveToParentViewController:self];
newVC.view.frame = self.containerView.bounds; // containerView is the IBOutlet to the container view in the storyboard
[self.containerView addSubview:newVC.view];
}
I'm having an issue passing a reference to a my data object. The presenting view controller has a reference to the data object. The modal view controller is hooked up to a navigation controller, and is its root view controller. Here is how I'm doing it:
Presenting VC:
- (IBAction)changeCustomerButtonPress:(UIButton *)sender {
UINavigationController *customersNC = [self.storyboard instantiateViewControllerWithIdentifier:#"customersNC"];
SCCustomersVC *customersVC = (SCCustomersVC *)[self.storyboard instantiateViewControllerWithIdentifier:#"customersVC"];
customersVC.dataObject = self.splitVC.dataObject;
//at this point, customersVC.dataObject exists
[self presentViewController:customersNC animated:YES completion:nil];
}
When the modal VC appears, self.dataObject is nil.
- (void)viewWillAppear:(BOOL)animated
{
//self.dataObject is nil here.
}
What is the correct way of doing this?
Does you navigation controller in IB have a root view controller? If, not it should. But presuming that you have one, you shouldn't be instantiating the SCCustomersVC, because the navigation controller will do that when it's instantiated. To pass the data, just get a reference to that controller with topViewController:
SCCustomersVC *customersVC = (SCCustomersVC *)[customerNC topViewController];
customersVC.dataObject = self.splitVC.dataObject;