How do I override the back navigationItem? - ios

In my RecordViewController, within the didSelectRowAtIndexPath I push a detailViewController (which inherits from UIViewController):
[[self navigationController] pushViewController:detailViewController animated:YES];
Once the DetailViewController appears I can see a Back navigationButton in the top left corner, which automatically pops the current view controller to get back to the previous ViewController.
Now I need to show a UIAlertView and ask the user, if the data should be saved or not.
And only when the user has made a decision, the current view controller should disappear.
My problem is if I put this code into viewWillDisappear, it is already too late. I can't stop the process while showing the UIAlertView. This needs to be intercepted the moment the user pressed the back button.
Is there a method I could override to achieve this?

Create a UIBarButtonItem:
UIBarButtonItem * backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: #"Back"
style: UIBarButtonItemStyleDone target: self action: #selector(onBackButtonTapped:)];
Assign it to left bar button item:
self.navigationItem.leftBarButtonItem = backBarButtonItem;
Implement onBackButtonTapped API:
- (void) onBackButtonTapped: (id) sender
{
// Display an UIAlertView
}
You may want to customize the back button. Please look into UIBarButtonItem for more details.

Instead of pushing a detail view controller, the usual way to gather data is to present one modally. That will give you 2 free spaces on the left and right of the (new) navigation bar to place a Save and Cancel button.
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
[self presentViewController:navigationController animated:YES completion:nil];
// detailViewController will have to set up buttons in its init
You can use a delegate protocol you create to handle save and cancel actions in the presenting (i.e. not the detail) view controller.

Overview: The idea is to have your own barbutton to intercept the backing out from the VC.
In your viewDidLoad you can do this
UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStyleDone target:self action:#selector(Back:)];
self.navigationItem.backBarButtonItem = backButtonItem;
Then your Back: method can do this
-(void)Back:(id) sender
{
//Your code for showing AlertView with delegate as self. Remember to conform to the UIAlertViewDelegate protocol.
}
Then put your save functionality in
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (selected buttonIndex is the one for saving data)
{
//save your data
}
//popViewController
}

Related

Triggering an event when user clicks "< Back" button

I have fairly simple UINavigationControllers with two screens: screen1 & screen2.
When the user is on screen2 and clicks < Back I want to trigger an event before screen1 is shown -- basically I want to fetch data from the server for screen1.
Is there a way to do this?
In your viewDidLoad do this:
UIBarButtonItem *backBarButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"icon_back.png"] style:UIBarButtonItemStylePlain target:self action:#selector(goBack:)];
self.navigationItem.leftBarButtonItem = backBarButton;
Implement the method goBack:
- (void)goBack:(id)sender {
//Do something
[self.navigationController popViewControllerAnimated:YES];
}
Hope this helps.. :)
If you're using storyboard, you can handle your back action in.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
Only by doing it in viewWillDisappear. Unfortunately it will be triggered also when you present a new screen over the current one, not just when you tap the back button. You could however add a custom back button, and place your code in it's action method. After the operation is finished, call [self.navigationController popViewControllerAnimated:YES]
-(void)viewWillDisappear:(BOOL)animated{
if (![self.navigationController.viewControllers containsObject:self]) {
NSLog(#"Back Event");
}
}
You can do in two way.
Create custom button and set event.
Check on viewWillDisappear when users pop the screen

Change the title of backbutton in NavigationBar

Following the examples of the many duplicates for this questions, I can't seem to get it right.
I have a UINavigationViewController that has a LoginViewController as the rootViewController. Here I got a button with a segue (push) to a LoginInfoViewController.
In LoginInfoViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
//null
NSLog(#"%#", self.navigationItem.backBarButtonItem);
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Test"
style:UIBarButtonItemStyleDone
target:nil
action:nil];
self.navigationItem.backBarButtonItem = backButton;
//not null, still the back button says: "Back"
NSLog(#"%#", self.navigationItem.backBarButtonItem);
}
You will need to set the backBarButtonItem of the controller you are going back to, not the controller you pushed. Move your code to the LoginViewController viewDidLoad method.
The navigation controller derives the back button for the navigation bar from the backBarButtonItem of the preceding controller in the stack. If the item is nil, it will use the value in the title property of same. If the title is too long to fit, the navigation bar may substitute the string "Back" in place of the title.
If your controller has a custom left bar button item, the navigation bar will ignore the backButtonItem property and title presenting the custom button instead.
Set the title of the back button on the view BEFORE. So, if you segue from LoginViewController, you set the back button title on the item before you segue to LoginInfoViewController
Example:
In the viewDidLoad method on LoginViewController:
UIBarButtonItem *newBackButton = [[UIBarButtonItem alloc] initWithTitle: #"Go back" style: UIBarButtonItemStyleBordered target: nil action: nil];
[[self navigationItem] setBackBarButtonItem: newBackButton];
This means you're setting the button on the LoginViewController, not on the LoginInfoViewController.

How to add UIBarButtonItem to NavigationBar while using UIPageViewController

Please forgive me, there are already a ton of questions on how to add a UIBarButtonItem to a NavigationBar programmatically but I just can't seem to get any of the solutions to work in my situation.
Here is the layout of a simple test app I have been working with to learn the UIPageViewController.
I have the page view controller working nicely with three unique viewControllers. I would now like to set unique rightBarButtonItems for each of the view controllers. I can easily set a common barButtonItem in the DetailViewController by simply doing this.
UIBarButtonItem *newButton = [[UIBarButtonItem alloc] initWithTitle:#"Whatever"
style:UIBarButtonItemStylePlain
target:self
action:#selector(doSomething)];
self.navigationItem.rightBarButtonItem = newButton;
Given that I need a unique button for each of the three pages in the pageViewController I am not able to set the button in the detailViewController. I have tried the code above in viewDidAppear of the three controllers but the buttons will not appear. I have also tried creating the button in the same way and then setting it like this with no luck.
DetailViewController *vc = [[DetailViewController alloc] init];
vc.navigationItem.rightBarButtonItem = newButton;
I know I'm close here, I'm just not sure how to specify I need to add a button to the NavigationBar of the NavigationController that is running the detailViewController that my contentViewControllers are embedded in. Any help would be great ESPECIALLY SNIPPETS.
As a side note I have tried a few other methods that have come up problematic.
Setting the button in viewDidLoad rather than viewDidAppear
This will not work because the viewDidLoad method is not called everytime you swipe from one page to the next. The viewDidAppear method is however so I am trying to set the button there. It is possible viewWillAppear is called everytime but I haven't checked.
Setting the button in the UIPageViewController delegate methods
This is problematic if the user flips through the pages to quickly the button will fail to change or fail to appear.
SOLUTION
It turns out I was close… Rather than creating the button in the UIPageViewController methods I simply needed to create a navigationItem property in each of my three view controllers. When the controllers are instantiated in the delegate methods I simply set the navigationItem property equal to that of the detailViewController. That way in my viewDidApper methods I could create the button. Then at viewWillDisappear I set the button to nil. You can also just create the button at viewdidLoad of the DetailViewController and change the text and action in viewDidAppear of the individual viewControllers. Here is a sample.
In the delegate methods
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
I call a helper method based on the index of the viewController. The helper method instantiates the view controllers when it is called. Once instantiated I set some properties including the navigationItem property.
-(FirstController *)controllerAtIndex:(NSUInteger)index
{
FirstController *fvc = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstPageController"];
fvc.imageFile = self.pageImages[index];
fvc.titleText = self.pageTitles[index];
fvc.pageIndex = index;
fvc.navItem = self.navigationItem;
return fvc;
}
Then in the viewDidAppear of the viewController I just do this
-(void)viewDidAppear:(BOOL)animated
{
UIBarButtonItem *newButton = [[UIBarButtonItem alloc]
initWithTitle:#"Whatever" style:UIBarButtonItemStylePlain target:self action:#selector(doSomething)];
navItem.rightBarButtonItem = newButton;
}
If you need the button to change each time you change the page you look at, it must be done in one of the delegate methods that you have tried. I suggest however, that instead of creating a new button and setting it to the navigation item, you simply change the title.
You can create the button in interface builder and link it to a property in your DetailViewController and then call setTitle:forState with UIControlStateNormal on the button every time you go to a new page.
If you need the button to do something different for each page, I would recommend checking the current page in the buttons action method rather than declaring a new one each time.
You can find the correct navigation item to set buttons/titles on by finding the page controller in one of its paged view controllers, then getting its nav item. e.g.
UIViewController *pageController = [[self.navigationController childViewControllers] lastObject];
pageController.navigationItem.title = #"My page's title";
It's really annoying that self.navigationItem doesn't work!
One of my colleagues experienced the same problem with page view controller.
As far as I understand, you wont' be able to add UIBarButtonItem to navigation bar of member view controllers in UIPageViewController. Reason being 'navigationController' is set to nil for UIPageViewController. To confirm, print value of [UIPageViewController navigationController].
However you can do one thing to overcome this issue. Please download example PhotoScroller.
And do following changes in AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// kick things off by making the first page
PhotoViewController *pageZero = [PhotoViewController photoViewControllerForPageIndex:0];
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:pageZero];
if (pageZero != nil)
{
// assign the first page to the pageViewController (our rootViewController)
UIPageViewController *pageViewController = (UIPageViewController *)self.window.rootViewController;
pageViewController.dataSource = self;
[pageViewController setViewControllers:#[nav]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
}
return YES;
}
And following changes in the PhotoViewController.m
- (void)loadView
{
.....
.....
self.title =#"Frogs";
UIBarButtonItem *newButton = [[UIBarButtonItem alloc] initWithTitle:#"Something" style:UIBarButtonItemStylePlain target:self action:#selector(doSomething)];
[newButton setTintColor:[UIColor redColor]];
self.navigationItem.rightBarButtonItem = newButton;
}
To avoid crash, you have to handle UIPageViewControllerDataSource delegate methods correctly in AppDelegate.m
Though, I wouldn't advice you to do above as it breaks the whole concept of UIPageViewController.
I hope this would be helpful.

How can you place a back button onto a navigation bar between 2 ViewControllers?

I'm trying to create a transition between two scenes, this is a dumbed down version of what I have in my production code :
Both are ViewController, the left one has a TableView inside it and when clicked it should transition to the right hand scene, passing along data from whatever cell was clicked.
Currently, with a modal segue I can tap the cell and it transitions correctly, however, I can't figure out how to place a back button onto the nav bar.
I'm transitioning from the cell to the 2nd view controller like so :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"toSecond" sender:self];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"toSecond"])
{
NSLog(#"Preparing segue for toSecond, setting some data on target scene");
NSIndexPath *path = [self.theTableView indexPathForSelectedRow];
MyData * myData = [myDataArray objectAtIndex:path.row];
// Obtain handles on the current and destination controllers
FirstController * startingViewController;
SecondController * destinationController;
startingViewController = (FirstController * ) segue.sourceViewController;
destinationController = (SecondController * ) segue.destinationViewController;
destinationController.someData = myData;
}
}
On the SecondController, I've tried amending the viewDidLoad method to programatically include a back button item as suggested in this previous SO question:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIBarButtonItem * back = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
[self.navigationItem setBackBarButtonItem:back];
}
So my question is, how can I get a back button onto that nav bar? Something like this :
Thanks
One (easy) way, using the storyboard, is to embed a navigation controller into your view that has the table. Make sure the correct view, the one with the table, is highlighted and then go to Editor > Embed In > Navigation Controller.
The back button will automatically be there when you click a row of your table. Like Dan said, make sure it is a push segue between the 2 views in your picture.
You will not be able to add a back looking button to a modally presented ViewController easily
If you just want to add a normal button to the left side of the bar, do the following
UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonSystemItemDone target:nil action:nil];
UINavigationItem *item = [[UINavigationItem alloc] initWithTitle:#"Title"];
item.leftBarButtonItem = rightButton;
item.hidesBackButton = YES;
[self.yourNavigationBar pushNavigationItem:item animated:NO];
If you insist of presenting your view modally and still want a back button style you could use
Three20
In place of where you have the view with the table, us a UINavigationController don't delete the view with the table, just in place of where you segue to it, or if it was the root controller, use the nav con. Make the view with the table the root view controller of the navigation controller, and then simply use a push segue instead of a modal segue, and you should automatically get the back button
The best way is to just use a UINavigationController as the parent of your table view controller, and use a push segue instead of a modal segue.
You can create two instances of UINavigationItem and tell the UINavigationBar about them by setting the bar's items property. Instance 0 represents the table view controller.
You can create a UIButton with type 101 (the undocumented back button type), and wrap it in a UIBarButtonItem using initWithCustomView:.

UINavigationController "back button" custom text?

The "back button" of a UINavigationController by default shows the title of the last view in the stack. Is there a way to have custom text in the back button instead?
From this link:
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:#"Custom Title"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
As Tyler said in the comments:
don't do this in the visible view controller, but in the view
controller that you'd see if you hit the back button
You can set the text in the Interface Builder:
Select the navigation item of the ViewController that the back button would return to:
In the utilities panel attribute inspector, enter your label for the Back Button:
I would prefer this approach over setting the title in code as in the accepted answer.
Also note, you need to do this in the view controller one level up the
stack. In other words, don't do this in the visible view controller,
but in the view controller that you'd see if you hit the back button.
--Tyler
I use this:
// In the current view controller, not the one that is one level up in the stack
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.navigationBar.backItem.title = #"Custom text";
}
I found a handy solution to this by simply setting the title of the controller before pushing another controller onto the stack, like this:
self.navigationItem.title = #"Replacement Title";
[self.navigationController pushViewController:newCtrl animated:YES];
Then, make sure to set the original title in viewWillAppear, like this:
-(void)viewWillAppear:(BOOL)animated
{
...
self.navigationItem.title = #"Original Title";
...
}
This works because the default behavior of UINavigationController when constructing the back button during a push operation is to use the title from the previous controller.
The title of the back button defaults to the previous view's title so a quick trick I use is to place the following code on the previous view's .m file.
-(void)viewWillAppear:(BOOL)animated {
// Set title
self.navigationItem.title=#"Original Title";
}
-(void)viewWillDisappear:(BOOL)animated {
// Set title
self.navigationItem.title=#"Back";
}
in your init method, add the following code:
- (id)initWithStyle:(UITableViewStyle)style {
if(self = [super init]) {
//...
UIBarButtonItem *customBackButton = [[UIBarButtonItem alloc] initWithTitle:#"Back"
style:UIBarButtonItemStylePlain
target:self
action:#selector(goBack)];
self.navigationItem.leftBarButtonItem = customBackButton;
[customBackButton release];
//...
}
return self;
}
then add a simple method, to allow viewcontroller dismissing:
-(void)goBack {
[self.navigationController popViewControllerAnimated:YES];
}
Add the following code in viewDidLoad or loadView
self.navigationController.navigationBar.topItem.title = #"Custom text";
I tested it in iPhone and iPad with iOS 9
Adding to rein's answer. Note from Apple's docs that the declaration of backBarButtonItem is this:
#property(nonatomic, retain) UIBarButtonItem *backBarButtonItem
Therefore, rein's answer will leak memory because the synthesized setter will retain the instance you pass it, which is never released explicitly. You can remedy this by using autorelease
self.navigationItem.backBarButtonItem =
[[[UIBarButtonItem alloc] initWithTitle:#"Custom Title"
style:UIBarButtonItemStyleBordered
target:nil
action:nil] autorelease]; //<-- autoreleased
Or you could point a variable at the instance so you can explicitly release it later:
UIBarButtonItem* item = ...
self.navigationItem.backBarButtonItem = item;
[item release];
Hope this helps!
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
}
I've discovered something interesting.
If you subclass the UINavigationController and override the pushViewController:animated: method and do something like this: (bear in mind that I'm using ARC)
UIBarButtonItem *backButton = [[UIBarButtonItem alloc]
initWithTitle: #"Back"
style: UIBarButtonItemStyleBordered
target: nil action: nil];
viewController.navigationItem.backBarButtonItem = backButton;
[super pushViewController:viewController animated:animated];
Then for all ViewControllers that are pushed with your navigation controller will have the "Back" button in them automatically. If you want to change the text for certain view controllers you can try and maybe cast the viewcontroller to a certain class or your own custom protocol (which your viewcontroller inherits from which could have a method like backButtonText or something silly like that) which can give you certain information on the viewcontroller that's coming in sothat you can customize the back button text for it. Now the back button text is taken care of in a place which should hold the responsibility solely. I have to admit that creating a new button to change the text sucks, but oh well.
Can anyone think of a reason why not to do it like this? Atleast you don't have to fiddle with viewcontroller titles or have to remember to create a new back button before pushing the viewcontroller on the navigation controller.
rein's answer works well.
Note that if you push more than one view controller, the changed back button title will appear for each of them, which may not be what you want.
In that case, you'll need to create the custom UIBarButtonItem each time you push a view controller.
Also, make sure you do it before pushing the view controller, otherwise you will get a screen hiccup as the title changes.
Expanding on Aubrey's suggestion, you can do this in the child view controller:
create two variables for storing the old values of the parent's navigationItem.title and the parent's navigationItem
UINavigationItem* oldItem;
NSString* oldTitle;
in viewDidLoad, add the following:
oldItem = self.navigationController.navigationBar.topItem;
oldTitle = oldItem.title;
[oldItem setTitle: #"Back"];
in viewWillDisappear, add the following:
[oldItem setTitle: oldTitle];
oldTitle = nil; // do this if you have retained oldTitle
oldItem = nil; // do this if you have retained oldItem
It's not perfect. You will see the the title of the parent view change as the new controller is animated in. BUT this does achieve the goal of custom labeling the back button and keeping it shaped like a standard back button.
Put this into you viewDidLoad, hope it will result into what you are looking for
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Close"
style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backBarButtonItem;
[backBarButtonItem release];
if You want to set title in ARRIVING controller (sometimes more logic..)
in swift 3 do:
func setBackButtonNavBar(title: String, delay: Double){
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: { () -> Void in
if let navBar = self.navigationController?.navigationBar{
navBar.backItem?.title = title
}
})
}
in upcoming controller:
override func viewDidLoad() {
super.viewDidLoad()
self.setBackButtonNavBar(title: "back", delay: 0.3)
}
usually I put self.setBackButtonNavBar in a controller extension.
I know this is an old question and the answers' kind of out updated!
The easy way is to do this in parent ViewController:
i.e the one that takes you to next view controller.
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Custom text here", style: .plain, target: nil, action: nil)
Doing this in code remove the back button style of the UINavigationConroller. If you add a Navigation Item in each of yours views, you can set the title of the back botton in the StoryBoard.

Resources