How to show back button on the RootViewController of the UINavigationController? - iphone

Here is my code. I want to put a back button on the opening rootviewController.
- (void)selectSurah:(id)sender {
SurahTableViewController * surahTableViewController = [[SurahTableViewController alloc] initWithNibName:#"SurahTableViewController" bundle:nil];
surahTableViewController.navigationItem.title=#"Surah";
surahTableViewController.navigationItem.backBarButtonItem.title=#"Back";
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:surahTableViewController];
[self presentModalViewController:aNavigationController animated:YES];
}

I don't believe it's possible to pop the root view controller off the navigation stack, but you can fake it with a UIButton added as the custom view of a UIBarButtonItem:
UIButton *b = [[UIButton alloc]initWithButtonType:UIButtonTypeCustom];
[b setImage:[UIImage imageNamed:#"BackImage.png"] forState:UIControlStateNormal];
[b addTarget:self action:#selector(back:) forControlEvents:UIControlEventTouchUpInside];
self.leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:b];
A suitable PSD of iOS UI elements can be found here.

Faizan,
Helium3 comment makes sense.
I suppose that your button is needed to dismiss the controller presented modally, is it true? Correct if I'm wrong.
If so, you could just create a new UIBarButtonItem and set is a left (or right) button for the UINavigationController navigationItem. To not break encapsulation create it in the viewDidLoad method for your SurahTableViewController controller.
- (void)viewDidLoad
{
[super viewDidLoad];
// make attention to memory leak if you don't use ARC!!!
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Close"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(close:)];
}
-(void)close:(id)sender
{
// to dismiss use dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
// dismissModalViewControllerAnimated: is deprecated
[self dismissViewControllerAnimated:YES completion:^{ NSLog(#"controller dismissed"); }];
}

Since the SurahTableViewController is a root view controller in a navigation controller you can't go back to the root because you're already there. Since you've presented it modally from something else, you need to put a button on the nav bar that has an IBAction which calls:
[self dismissModalViewControllerAnimated:YES];

Appearance and behavior of a back button in a UINavigationController relies on interaction between a stack of UINavigationControllers. Putting a back button on the first controller breaks this convention, there's nothing to go back to, which is why your code isn't working.
You'll need to manually add UIBarButtonItem to the title bar code like:
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(back:)];
If you truly want it to look like a back button, you'll need to manually create the UIBarButtonItem with an image that mirrors the back button.
Another suggestion though, as it looks like you are attempting to use a back button to dismiss a modal view controller, I'd stick with something more conventional like a "Close" or "Done" button to close the modal view controller. A back button is really more appropriate for navigating a stack of UINavigationControllers.

Here's the answer I think everyone was looking for.
Simply provide an empty UIViewController as the rootViewController of your UINavigationController subclass in a custom initializer. Then push your actual rootViewController without animation before presenting:
init(backButtonRootViewController: UIViewController) {
super.init(rootViewController: UIViewController())
interactivePopGestureRecognizer?.isEnabled = false
pushViewController(backButtonRootViewController, animated: false)
}
You'll also want to disable the interactivePopGestureRecognizer to prevent the user from swiping back to the empty rootViewController.
Then in extend your subclass to implement the shouldPop method of UINavigationBarDelegate and dismiss the navigation controller when the back button is pressed on the rootViewController:
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if children.count >= 2 && children[1] == topViewController {
presentingViewController?.dismiss(animated: true, completion: nil)
return false
} else {
return true
}
}
Works like a charm!

For Swift, try something like:
override func viewDidLoad() {
super.viewDidLoad();
self.navigationItem.hidesBackButton = true;
let backButton = UIBarButtonItem(
image: UIImage(named: "my_back_asset"), style: .plain,
target: self, action: #selector(self.onBack));
self.navigationItem.leftBarButtonItem = backButton;
}
#objc private func onBack() {
let controller = self.navigationController?.popViewController(animated: true);
if controller == nil {
// Handle pressing back when on root here.
}
}
Note to change "my_back_asset" to your own back-button image.

Related

Make done button in the UINavigationBar appear when the row in UITableView is selected

I want to make a done button appear UINavigationBar when any row in my UITableView is selected and I want this button to perform an action performSegueWithIdentifier.
Any ideas on how to implement it?
Add the following to your tableView:didSelectRowAtIndexPath: method:
//add done button to navigation bar
UIBarButtonItem *doneBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(userPressedDone)];
self.navigationItem.rightBarButtonItem = doneBarButtonItem;
Then have a method like this somewhere in your view controller:
-(void)userPressedDone {
//perform segue
[self performSegueWithIdentifier:#"mySegue" sender:nil];
}
I would imagine that in your -didSelectRowAtIndexPath: method you would add a UIBarButtonItem to the right or left bar button item of the navigationItem of your view controller.

UINavigationController and titleView

My application fit inside a UINavigationController.
Simply I setted up a MyUIViewController as rootViewController of navigationController and than I set up a customTitleView for the navigationItem of myViewController inside viewDidLoad.
Now when I push a newViewController I expect to see the previous customTitleView (as described by Apple NavigationController reference) but it doesn't.
What's wrong?
A little part of code below and the Apple UINavigationController Reference
"If the new top-level view controller has a custom title view, the navigation bar displays that view in place of the default title view. To specify a custom title view, set the titleView property of the view controller’s navigation item."
- (void)viewDidLoad {
[super viewDidLoad];
[self.navigationItem setTitleView:customTitleView];
}
Maybe the "default title view" means "nothing"? I interpreted it as the previous titleView.
EDIT
I'm trying a work around but I have some other issues.
The work around is:
In every ViewController that I push inside the NavigationController I setup in viewDidLoad the titleView getting it from the rootViewController
UiView *originalContainer = [[[self.navigationController.viewControllers objectAtIndex:0] navigationItem] titleView]
I create a newTitleView and I put inside the originalContainer.subviews (UIButton) cause I need the Target action from it.
All works perfectly for the first pushed controller but if I push another viewController in the stack (the second one pushed) I loose every reference to the navigationController. Every instance variables are nil.
This values are getted inside the viewDidLoad
firstViewControllerPushed.navigationController = (UINavigationController*)0x6693da0
firstViewControllerPushed.parentViewController = (UINavigationController*)0x6693da0
secondViewControllerPushedFromFirstViewControllerPushed.navigationController = 0x0
secondViewControllerPushedFromFirstViewControllerPushed.parentViewController = 0x0
It seems that the secondViewControllerPushed lives nowhere!!
How it's possible?
I double checked that I correctly push the viewController instead of present it modally
Couse this issue I'm not able to make a right setup of the newTitleView for the secondViewControllerPushed.
This is a hack in a half but I feel it gives you a bit more control in the viewController you are working in. So below is a small method I have setup and then I just call it in viewDidLoad - [self setUpNavBar];
And using [UIButton buttonWithType:101] is perfectly fine as it passed Apple Validation.
- (void) setUpNavBar
{
[self.navigationController setNavigationBarHidden: NO animated:YES];
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
self.title = #"Settings";
UIButton* backButton = [UIButton buttonWithType:101]; // left-pointing shape
[backButton addTarget:self action:#selector(backAction) forControlEvents:UIControlEventTouchUpInside];
[backButton setTitle:#"Back" forState:UIControlStateNormal];
UIBarButtonItem* backItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
self.navigationItem.leftBarButtonItem = backItem;
[backItem release];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Logout"
style:UIBarButtonItemStylePlain target:self
action:#selector(logoutAction)] autorelease];
}

How do I build UITableViewGrouped in a Modal View?

I want to build Grouped View in Modal View which is destination state from "+" button on the Navigation bar in main table view.
I have written this code:
-(void)viewDidLoad
{
//title
self.title = #"Set";
//addBtn
UIBarButtonItem *addBtn = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(toggleEditing)];
self.navigationItem.rightBarButtonItem = addBtn;
[addBtn release];
}
-(IBAction)toggleEditing
{
}
create a subclass of UITableViewController named MyTableViewController
use presentModalViewController:animated: in your current viewController
-(IBAction)toggleEditing {
MyTableViewController *tableViewController = [[[MyTableViewController alloc] initWithStyle:UITableViewStyleGrouped] autorelease];
[self presentModalViewController:tableViewController animated:YES];
}
And you will need a delegate method that tells the viewController from where you showed the modal viewcontroller that a row was selected.

Back button of Navigation Controller does not work!

For some reason, if I try to go back to the main menu using the back button on the upper left corner, only the title returns to the previous menu, but not the view controller. View controller would return to the previous menu only if I explicitly call popViewControllerAnimated using some other button.
Is there anyway to solve this? I think I've coded something wrong. Tried googling but couldn't find any cases like mine.
I'm getting the exact same problem. Here is my code:
- (IBAction) showGameView:(id) sender {
gameView = [[TCGameViewController alloc] initWithNibName:#"TCGameViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:gameView animated:YES];
[gameView release];
}
And when I am done with gameView, I do this:
[self.navigationController setNavigationBarHidden:NO animated:YES];
But all it does when I push the 'back' button is cycle through the navigation bar, but never pops the view. I don't even know how to debug it.
In my other view, "infoView" I call the same code as before except the NavBar is never hidden, but it works just fine.
helps!
This problem can occur when you override the following method in your custom view controller:
- (UINavigationItem*)navigationItem
But you don't specify a UIBarButtonItem for the leftBarButtonItem property of the returned UINavigationItem.
If you use a custom navigationItem, and want the standard back button functionality, you could add a method as follows (remember that every UIViewController has a reference to the navigationController that containts it):
- (void)backButtonTapped
{
[self.navigationController popViewControllerAnimated:YES];
}
And then setup part of the custom navigationItem as follows:
- (UINavigationItem*)navigationItem
{
UIBarButtonItem* newLeftBarButton = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(backButtonTapped)];
UINavigationItem* navigationItem = [[[UINavigationItem alloc] init] autorelease];
Hope this helps.

Is there a better way to hide the backBarButtonItem than this?

I have a way of hiding the back button used by the navigation controller. It's set by the previous controller, not the one managing the current view, and that makes it tricky to get to. I needed to do this in editing mode so that I could prevent the user from navigating away from the screen.
if(self.editing) {
// Get rid of the back button
UIView *emptyView = [[UIView alloc] init];;
UIBarButtonItem *emptyButton = [[[UIBarButtonItem alloc] initWithCustomView:emptyView] autorelease];
[self.navigationItem setLeftBarButtonItem:emptyButton animated:YES];
} else {
// Restore the back button
[self.navigationItem setLeftBarButtonItem:nil animated:YES];
}
Is there a better way to do this?
use this to hide back button
[self.navigationItem setHidesBackButton:YES]
use this to show back button
[self.navigationItem setHidesBackButton:NO]
Here's the method I use in my view controller to show and hide the back button when editing is enabled and disabled:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
if (editing) {
// Disable the back button
[self.navigationItem setHidesBackButton:YES animated:YES];
}
else {
// Enable the back button
[self.navigationItem setHidesBackButton:NO animated:YES];
}
[super setEditing:editing animated:animated];
}
Make an outlet with strong (not weak as default) of the bar button from the storyboard to your view controller.
The purpose is not to loose the reference when you set the left/right bar button to nil.
Swift5:
self.navigationItem.setHidesBackButton(true, animated: false)