navigation bar not showing with a programmatically made UINavigationController - ios

I have a navigation controller named navController made programmatically in my modal view controller during its viewDidLoad:
self.navController = [[UINavigationController alloc] initWithRootViewController:self];
self.navController.view=self.view;
[self setView:self.navController.view];
But when i launch the modal view controller i dont see the navigation bar, just the standard view i made in IB. Whats wrong?

Your solution cannot work.
Suppose that you have your modal controller called ModalViewController. It's a simple UIViewController linked with a xib created interface.
Now, at some point you need to present ModalViewController modally. As you wrote in your specification, I think you want to use also a UINavigationController and control its navigation bar.
The code to do this could be the following, where presentModally could be a method that it's not contained in ModalViewController.
- (void)presentModally:(id)sender {
ModalViewController *modalController = [[ModalViewController alloc] initWithNibName:#"ModalView" bundle:nil];
// Create the navigation controller and present it.
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:modalController];
[self presentViewController:navigationController animated:YES completion: nil];
}
Now, within viewDidLoad of your ModalViewController you have access to navigationController property. In this manner you can control navigationController behaviour. For example:
- (void)viewDidLoad
{
[super viewDidLoad];
// the code changes the title for the navigation bar associated with the UINavigationController
self.title = #"Set from ModalViewController";
}
Some notes
To understand how UINavigationController works read UINavigationController class reference
To understand how modal controllers work read Modal view controllers documentation
The code I provided is a simple example and only demonstrative (I've written by hand so check for syntax). You need to make attention to memory management and how to present modal controllers. In particular, as Apple documentation suggests, to present modal controllers you need to follow these steps:
Create the view controller you want to present.
Set the modalTransitionStyle property of the view controller to the desired value.
Assign a delegate object to the view controller. Typically the delegate is the presenting view controller. The delegate is used by the presented view controllers to notify the presenting view controller when it is ready to be dismissed. It may also communicate other information back to the delegate.
Call the presentViewController:animated:completion: method of the current view controller, passing in the view controller you want to present.
Trigger (when necessary) some action to dismiss the modal controller.
Hope it helps.

Related

Xcode 6 - Added Navigation Controller to storyboard, but not appearing in app

I added a Navigation Controller to my storyboard and it appears like so:
Now in the table view controller, I gave the TableViewController a storyboard id and class to a TableViewController Controller
When I run my app, I don't see the Navigation Bar at the top. This has been extremely frustrating and can't find a solution anywhere. PLEASE HELP
To get to the scene, someone clicks a button and this code runs and it goes to my Table View Controller:
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
LHFileBrowser *LHFileBrowser = [storyBoard instantiateViewControllerWithIdentifier:#"FileBrowser"];
[self.navigationController pushViewController:LHFileBrowser animated:YES];
[self presentViewController:LHFileBrowser animated:YES completion:nil];
The error is in your code.
If you want to (modally) present a view controller when the user presses a button, you need to present the navigation controller (which will contain the table view controller), not the table view controller itself.
Right now, you're presenting the view controller, which won't show it being embedded in a navigation controller.
Also, you're mixing up two different approaches, by trying to push a view controller onto a navigation controller stack, and also presenting the view controller.
Code Sample:
Here's what you apparently mean to do:
UIStoryboard *storyboard = self.storyboard;
UINavigationController *navigationController = [storyboard instantiateViewControllerWithIdentifier:#"MyNavigationControllerID"];
LHFileBrowser *rootViewController = [navigationController topViewController];
// Configure your LHFileBrowser view controller here.
rootViewController.someProperty = ...;
// Modally present the embedded view controller
[self presentViewController:navigationController animated:YES completion:nil];
If you want to change the presentation or transition style, you can set those details in your storyboard.
You didn't explain why you had to programmatically add buttons, but Storyboard segues would have instantiated and presented an embedded view controller for you, without you having to have done it in code.
The more you can do in Storyboard, the less code you have to maintain, support, and update, and the more likely your app will still work properly when a new SDK is released.
Update:
The better way to do this is to let Storyboard do it for you, by adding a segue from the button to the navigation controller that you want to present.

Subview UINavigationController Leak ARC

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.

How do I instantiate a storyboard with a given root viewcontroller and initial viewcontroller?

I have a storyboard in my application with a navigation controller and several views. This automatically puts a navigation bar with a back button into any views that are not the root view.
However, sometimes I navigate away from this storyboard to an individual nib. I want to navigate back to the storyboard, but not necessarily to the original root view. I currently use this method to do so:
+(void) TransitionOnStoryboard:(NSString*)storyboard to:(NSString*)identifier withViewController:(UIViewController*)viewController
{
UIStoryboard *sb = [UIStoryboard storyboardWithName:storyboard bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:identifier];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[viewController presentViewController:vc animated:YES completion:NULL];
}
This shows the view I want but without the navigation bar. How do I specify my navigation controller or root view, such that the app knows to put a navigation bar with a back button in?
Thanks
The answer is to leave your navigation controller underneath the view controller you add from a nib.
Present the nib as a full0-screen modal. That gets rid if your navigation bar, as desired. From that new view controller, you can push more modals, add a navigation controller, or whatever.
Note that you could do all of this and stay inside your storyboard as well.
Once you are done, dismiss the modal to reveal your navigation controller, and you are back in business with your storyboard. You can push a new view controller onto your navigation controller without animation and it should appear as the front-most VC when you pop the modal that came from a nib.
I'm sure that this isn't the ideal way to solve this problem, but it did work very nicely for me.
Essentially, I removed all the views from the view controller that had been generated since I navigated away from the storyboard, but before the current view and popped the current view. In this case, these views were of one class (CheckboxListViewController) and so could be removed quite simply as below:
+(void) navigateToMainMenu:(UINavigationController*)navigationController
{
[QuickView removeFromNavigationController:navigationController allOfViewControllerWithClass:[CheckboxListViewController class]];
[navigationController popViewControllerAnimated:YES];
}
+(void) removeFromNavigationController:(UINavigationController *)navigationController allOfViewControllerWithClass:(Class)viewControllerClass
{
NSMutableArray *keptViewControllers = [[NSMutableArray alloc]init];
for (UIViewController *viewController in navigationController.viewControllers)
if (![viewController isKindOfClass:viewControllerClass])
[keptViewControllers addObject:viewController];
navigationController.viewControllers = keptViewControllers;
}
(note- QuickView is the name of the class that contains these methods.).
Any other classes that you do not want your pop to navigate back to can be removed by calling:
[QuickView removeFromNavigationController:navigationController allOfViewControllerWithClass:[YourClassName class]];
In the navigateToMenu method.

How can I present a 'standalone' View Controller ('forgetting' the previous one)

I have a regular UINavigationController with a couple of views attached, which are working perfectly fine. Its RootViewController has a custom Menu-button on the top left, at the same place as the "Back"-button is on the attached views. When clicking this menu-button, the menu appears and presents five options.
Obviously, by clicking one of these option, you would be presented with the ViewController for that option.
I want to completely 'forget' the current ViewController, and move on to this new controller. Usually, I would do something like [self presentViewController....]; or [self.navigationController push..];, but in these methods the current ViewController will, I think, always exists 'below' the new presenting viewController (as you would return to this instantiation if using [self dismissViewController..];, I don't want this).
In the presenting ViewController there will be a menu-option to return back to the original controller, but I still want this to be a clean instantiation of it, and not just popping. By thinking ahead in time, I figured I would potentially create an infinite number of ViewControllers on top of each other by using the methods I know of this way.
I entered the world of iOS after the era of ARC began, so I have no clue how to release or deallocate such views, which I assume has relevance here.
The second View Controller is also supposed to be a root in a UINavigationController, and I'm not sure if it's best to use the same UINavigationController, or if I should present a new one, and dismiss the old. Essentially, I would like to replace the Navigation Controller's rootViewController from the rootViewController, but I don't see how that would be possible. Or possibly push to ViewController2, and then popping the rootViewController out of the hierarchy, leaving the new ViewController as the root, but then I assume I'd have problems with the navigational back-button(if it's even possible).
I figured it's just as easy to let ViewController2 be root at its own NavigationController, and presenting this NavigationController from ViewController1. The problem is, I want to completely remove everything that has to do with ViewController1 and its NavigationController from memory after presenting ViewController2, but I have no idea how.
I'm open to other solutions to my situation, but I'd also like an answer to how I can completely 'forget' a view after presenting another on general basis.
If you want to "forget" controllers, you can just replace the window's root view controller with a new one. The original one will be deallocated if you don't have any other strong pointers to it. I'm not sure I understand all of what you're trying to do, but for example, if you want controller 1 and controller 2 to both be root view controllers of a navigation controller, and you don't want controller 1 around when you switch to 2, then do something like this from controller one:
SecondViewController *second = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:second];
self.view.window.rootViewController = nav;
This will switch out the controllers, and controller 1 and its navigation controller will be deallocated (assuming that the only thing with a strong pointer to the navigation controller was the window, through its rootViewController property).
Create a menu UIViewController and add it as a root to the UINavigationController on launch. Add 1st UIViewController as a child controller to menu UIViewController when viewDidLoad of menu controller is called. When you click menu to show 2nd UIViewController, remove the 1st UIViewController from menu view controller and add 2nd UIViewController to child of the menu view controller. You can put NSLog in both, 1st and 2nd view controller's dealloc method to check if its released or not. Logic is like this
//inside menuvc class
#interface MenuVC{
UIViewController * currentVC; // current child controller to menuVC
}
-(void)viewDidLoad{
[self addChildController:firstVC]; //to add view controller 1 intially
currentVC = firstVC;
}
-(void)add2ndChildController{
[currentVC removeFromParentViewController];
[self addChildController:secondVC]; //to add view controller 2 when needed
currentVC = secondVC;
}
// dealloc of 1st vc
-(void)dealloc{
NSLog(#"first vc released");
}
I just wrote some sample logic of what I explained before, you have to generalize this logic if you feel its right for you. Hope it helps :)

Setting up a UINavigation controller and various view loads

I have setup a UINavigation controller that uses the AppDelegate as the main point of contact.
I have different methods which run such as presentHomeViewController, presentLoginViewController, which push the different view controllers to the Navigation Controller.
App Delegate - didFinishLaunching
welcomeViewController = [[MyWelcomeViewController alloc] initWithNibName:#"MyWelcomeViewController" bundle:nil];
navController = [[UINavigationController alloc] initWithRootViewController:welcomeViewController];
navController.navigationBarHidden = YES;
self.revealSideViewController = [[PPRevealSideViewController alloc] initWithRootViewController:navController];
[self.revealSideViewController setDirectionsToShowBounce:PPRevealSideDirectionNone];
[self.revealSideViewController setPanInteractionsWhenClosed:PPRevealSideInteractionContentView | PPRevealSideInteractionNavigationBar];
self.window.rootViewController = self.revealSideViewController;
Is this the correct process for this?
- (void)presentHomeViewController {
// We start by dismissing the ModalViewConrtoller which is LoginViewController from the welcomeview
[self.welcomeViewController dismissModalViewControllerAnimated:YES];
// Check if the home view controller already exists if not create one
if (!self.homeViewController) {
NSLog(#"presentHomeViewController- Creating the Home View controller");
homeViewController = [[MyHomeViewController alloc] initWithNibName:#"MyHomeViewController" bundle:nil];
}
// Push the homeViewController onto the navController
NSLog(#"presentHomeViewController");
self.navController.navigationBarHidden = NO;
[self.navController setTitle:#"Home"];
[self.navController pushViewController:homeViewController animated:NO];
If I then add the following to a different class :
[self.navigationController pushViewController:accountViewController animated:NO];
No view is pushed to the stack, should I control all the movement within the AppDelegate as I have been doing, or is there betters way to approach this?
EDIT
Thanks for posting your code. So, to address your final question first, I don't recommend controlling your navigation stack from the app delegate. You should be controlling the stack from the view controllers that are the children of the navigation controller.
To that point, remember the hierarchy of view controllers: UINavigationController inherits from UIViewController, and UIViewController has properties defined for all the things you'd see in a navigation layout such navigation items and title. More importantly, it also has properties for its parent view controllers, the view controller that presented it, and its navigation controller. So, considering the hierarchy, your app delegate should only instantiate the navigation controller's root VC and the nav controller itself, and then subsequently set the nav controller's root VC.
From there, you should be pushing and popping other VCs from the VCs themselves. Remember, every VC has a property that's automatically set to point at the navigation controller it's a part of. That's why [self.navigationController pushViewController:] works. For instance, if I have a nav controller whose root VC is a UITableViewController, and tapping on one of the items in the table view pushed a new VC onto the stack, I would push that VC from the table VC and not from the nav controller class or the app delegate.
Sorry if that's confusing. Please let me know if that needs clarification and I'll give it my best. Otherwise, hopefully that gets you on the right track.

Resources