Setting action for back button in navigation controller
Solution 1
Try putting this into the view controller where you want to detect the press:
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
// back button was pressed. We know this is true because self is no longer
// in the navigation stack.
}
[super viewWillDisappear:animated];
}
Solution 2
I've implemented UIViewController-BackButtonHandler extension. It does not need to subclass anything, just put it into your project and override navigationShouldPopOnBackButton
method in UIViewController
class:
-(BOOL) navigationShouldPopOnBackButton {
if(needsShowConfirmation) {
// Show confirmation alert
// ...
return NO; // Ignore 'Back' button this time
}
return YES; // Process 'Back' button click and Pop view controler
}
Solution 3
Unlike Amagrammer said, it's possible. You have to subclass your navigationController
. I explained everything here (including example code).
Solution 4
Swift Version:
(of https://stackoverflow.com/a/19132881/826435)
In your view controller you just conform to a protocol and perform whatever action you need:
extension MyViewController: NavigationControllerBackButtonDelegate {
func shouldPopOnBackButtonPress() -> Bool {
performSomeActionOnThePressOfABackButton()
return false
}
}
Then create a class, say NavigationController+BackButton
, and just copy-paste the code below:
protocol NavigationControllerBackButtonDelegate {
func shouldPopOnBackButtonPress() -> Bool
}
extension UINavigationController {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
// Prevents from a synchronization issue of popping too many navigation items
// and not enough view controllers or viceversa from unusual tapping
if viewControllers.count < navigationBar.items!.count {
return true
}
// Check if we have a view controller that wants to respond to being popped
var shouldPop = true
if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
shouldPop = viewController.shouldPopOnBackButtonPress()
}
if (shouldPop) {
DispatchQueue.main.async {
self.popViewController(animated: true)
}
} else {
// Prevent the back button from staying in an disabled state
for view in navigationBar.subviews {
if view.alpha < 1.0 {
UIView.animate(withDuration: 0.25, animations: {
view.alpha = 1.0
})
}
}
}
return false
}
}
Solution 5
It isn't possible to do directly. There are a couple alternatives:
- Create your own custom
UIBarButtonItem
that validates on tap and pops if the test passes - Validate the form field contents using a
UITextField
delegate method, such as-textFieldShouldReturn:
, which is called after theReturn
orDone
button is pressed on the keyboard
The downside of the first option is that the left-pointing-arrow style of the back button cannot be accessed from a custom bar button. So you have to use an image or go with a regular style button.
The second option is nice because you get the text field back in the delegate method, so you can target your validation logic to the specific text field sent to the delegate call-back method.
Related videos on Youtube
Parrots
Freelance designer and developer, focusing on web and iOS apps. Gamer, runner, pilot, and all around nerd.
Updated on June 04, 2020Comments
-
Parrots about 4 years
I'm trying to overwrite the default action of the back button in a navigation controller. I've provided a target an action on the custom button. The odd thing is when assigning it though the backbutton attribute it doesn't pay attention to them and it just pops the current view and goes back to the root:
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle: @"Servers" style:UIBarButtonItemStylePlain target:self action:@selector(home)]; self.navigationItem.backBarButtonItem = backButton;
As soon as I set it through the
leftBarButtonItem
on thenavigationItem
it calls my action, however then the button looks like a plain round one instead of the arrowed back one:self.navigationItem.leftBarButtonItem = backButton;
How can I get it to call my custom action before going back to the root view? Is there a way to overwrite the default back action, or is there a method that is always called when leaving a view (
viewDidUnload
doesn't do that)?-
PartySoft almost 13 yearsaction:@selector(home)]; needs a : after the selector action:@selector(home:)]; otherwise it won't work
-
mbm29414 almost 12 years@PartySoft That's not true unless the method is declared with the colon. It's perfectly valid to have buttons call selectors that don't take any parameters.
-
Sam over 11 yearspossible duplicate of Stopping the self.navigationItem.leftBarButtonItem from exiting a view
-
JohnK about 11 yearsWhy wouldn't Apple provide a button with style shaped like a back button? Seems pretty obvious.
-
Jiri Volejnik about 6 yearsLook at the solution in this thread
-
Taras almost 5 yearsI did it this way show deсision
-
Miguel Gallego about 4 yearsI post this stackoverflow.com/a/62275894/4084902 to overwrite the default action of the back button
-
-
John Ballinger almost 15 yearsI agree you could photoshop and I think I might do this if I really wanted it but now have decided to change the look and feel a tiny bit to get this to work the way I want.
-
Amagrammer almost 15 yearsYes, except that the actions are not triggered when they are attached to the backBarButtonItem. I don't know if this is a bug or a feature; it's possible that even Apple doesn't know. As for the photoshopping exercise, again, I would be wary that Apple would reject the app for misusing a canonical symbol.
-
Parrots almost 15 yearsYeah the problem is I want it to look like the normal back button, just need it to call my custom action first...
-
Kuba Suder over 14 yearsApple's documentation (developer.apple.com/iphone/library/documentation/UIKit/…) says that "This class is not intended for subclassing". Though I'm not sure what they mean by this - they could mean "you shouldn't normally need to do that", or they could mean "we will reject your app if you mess with our controller"...
-
Adam Eberbach about 14 yearsThis is certainly the only way to do it. Wish I could award you more points Hans!
-
JosephH almost 14 yearsCan you actually prevent a view from exiting using this method? What would you make the popViewControllerAnimated method return if you wanted the view not to exit?
-
HansPinckaers almost 14 yearsYeah, you can. Just don't call the superclass method in your implementation, be aware! You shouldn't do that, the user expects to go back in the navigation. What you can do is ask for an confirmation. According to Apples documentation popViewController returns: "The view controller that was popped from the stack." So when nothing is popped your should return nil;
-
Amagrammer over 13 yearsApparently I was mistaken in my original response. It just goes to show, sometimes "good" answers are wrong answers...
-
boliva over 12 yearsthis is a slick, clean, nice and very well thought workaround
-
JOM over 12 yearsWhoo, hope this is future-proof! Works now beautifully, could remove custom subclass, which was doing nothing but trapping popViewControllerAnimated == wasn't very reusable
-
The iOSDev about 12 yearsawesome answer @William this is great i think you have done alote research thanks!
-
matm almost 12 years+1 great hack, but does not offer control over animation of pop
-
SAHM over 11 yearsDoesn't work for me if I send a message to the delegate through a button and the delegate pops the controller - this still fires.
-
Chase Roberts over 11 yearsAnother problem is that you can't differentiate if the user pressed the back button or if you programatically called [self.navigationController popViewControllerAnimated:YES]
-
deepwinter over 11 years@HansPickaers I think your answer about preventing a view from exiting may be somewhat incorrect. If I display a 'confirm' message from the subclasses implementation of popViewControllerAnimated:, the NavigationBar still animates up one level in the tree regardless of what I return. This seems to be because clicking the back button calls shouldPopNavigationItem on the nav bar. I am returning nil from my subclasses method as recommendd.
-
Shog9 over 11 yearsHeads-up: this answer has been merged in from a duplicate.
-
Shog9 over 11 yearsHeads-up: this answer has been merged in from a duplicate.
-
Shog9 over 11 yearsHeads-up: this answer has been merged in from a duplicate.
-
Shog9 over 11 yearsHeads-up: this answer has been merged in from a duplicate.
-
Shog9 over 11 yearsHeads-up: this answer has been merged in from a duplicate.
-
Shog9 over 11 yearsHeads-up: this answer has been merged in from a duplicate.
-
Howard Pautz over 11 yearsThis works good for simple storyboard/Nib views - if there's no code-controlled navigation (as delirious, JPK, and Chase Roberts mention), is it reliable to do say update/save SQLite stuff that's in a managed context ?
-
JohnK about 11 yearsAgreed, @ChaseRoberts. The the technique can't differentiate between the back button and a Cancel button that just pops the current navigation item.
-
bogen almost 11 yearsScrap everything else. Best answer. All the other answers suck
-
Ramiro González Maciel over 10 yearsThis is the cleanest solution I've seen, better and simpler than using your own custom UIButton. Thanks!
-
B.S. over 10 yearsDoes Apple allow to change shouldPopItem? I think that app can be rejected
-
onegray over 10 yearsThe
navigationBar:shouldPopItem:
isn't a private method since it's a part ofUINavigationBarDelegate
protocol. -
Sam over 10 yearsbut does UINavigationController already implement the delegate (to return
YES
)? or will it in the future? subclassing is probably a safer option -
Sam over 10 years@onegray out of interest - why do you
dispatch_async
a pop? wouldn't returningshouldPop
from theshouldPopItem
method be enough? -
onegray over 10 yearsOriginally, the
UINavigationController
had its own handler of this method, but now it is overridden by the category and it may not work properly. That’s why it needs to use an explicit call of thepopViewController
instead. Thedispatch_async
is needed to break the calling stack. -
Sam over 10 yearsAh yes, I nipped back on here as I'd just worked that out :) Interestingly
UINavigationController
doesn't respond to this method (it's a protocol that it doesn't seem to implement) - I did a quick check and there doesn't seem to be any method (even hidden). So if I use your method in a subclass, base calling just winds up in an infinite loop. -
ambientlight over 10 years@JosephH You can do it in your subclass, but not in this method. You have to make your subclass to be a delegate of UINavigationBarDelegate(UINavigationController doesn't implement it) Then you implement the method -navigationBar:shouldPopItem:, which will be called each time you press BackButton
-
C4 - Travis over 10 yearsgreat answer, works for adding a transition and a pop animation.
-
arlomedia over 10 yearsI think you mean to say subclass the UINavigationController class and implement a shouldPopItem method. That is working well for me. However, that method should not simply return YES or NO as you would expect. An explanation and solution is available here: stackoverflow.com/a/7453933/462162
-
boliva over 10 yearsI just implemented this (pretty cool BTW) in iOS 7.1 and noticed that after returning
NO
the back button stays in a disabled state (visually, because it still receives and reacts to touch events). I worked around it by adding anelse
statement to theshouldPop
check and cycling through the navigation bar subviews, and setting thealpha
value back to 1 if needed inside an animation block: gist.github.com/idevsoftware/9754057 -
Srikanth over 10 yearsThis is one of the best extension which I have ever seen. Thank you so much.
-
frin over 10 yearsExcellent, I had issue with nested navigation controllers and had parent navigation bar hidden but wanted to show back arrow on child navigation bar, I accomplished this with dummy top view controller and pushed my view controller as second without animation. I overrode the back action so it pops on the parent navigation instead of showing the dummy view controller. Works great as intended.
-
Nick N about 10 yearsThis would be a great one to become a cocoapod enabled project.
-
SmileBot about 10 yearsThis doesn't tell you the cause. An unwind segue could be the cause not a back button tap.
-
danfordham almost 10 yearsThis is the best solution out there, that keeps the '< Back' button styling and doesn't require you to subclass UINavigationController.
-
Taskinul Haque almost 10 yearsthis works well : as long as you don't need to call an AlertView on the back button push, it executes the command, but it continues with the back button press, while it does so
-
kjam almost 10 yearsWouldn't it be better to simply call original implementation if navigationShouldPopOnBackButton returns YES? My code already has a subclass of UINavigationController, so calling super is pretty straightforward. But for category case, you can do method swizzling in category's +load method, thus being able to save original implementation.
-
onegray almost 10 yearsYes, subclassing is an option and it is proposed in another answer. Using objc-runtime might be helpful, but it needs much more code, so not sure if it is reasonable.
-
Travis Elliott almost 10 yearsThis does work I suppose. However I need to pass data to the root view which is navigated to on the back button. How would I get that root view object so I can pass it data? This is easy in a prepareForSegue since you can get destinationViewController from the passed UIStoreboardSegue.
-
BHuelse over 9 yearsThis solution works great. No need to style overridden button
-
Nhat Dinh over 9 yearsThis is a little bit treat. If for another reason , viewWillDisappear called -> the program will not work correctly. (Such as when i push a image picker controller -> viewWillDisappear will be called.)
-
anders over 9 yearsThis is exactly what I was looking for! Thanks!
-
Mike Gledhill over 9 yearsYup, a vital piece of functionality, brilliantly coded, and still missing in iOS 8. Great stuff.
-
hEADcRASH over 9 yearsJust an FYI: Swift version:
if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
-
James Parker about 9 yearsWhy this is such a difficult thing to find and handle. Thank you! This is the answer for so many posts.
-
stkent almost 9 yearsNeater swift:
if !contains(navigationController.viewControllers as! [UIViewController], self)
-
iRiziya almost 9 yearsWorked like a charm! Thanks :)
-
Justin Milo almost 9 yearsMake sure your view controller sets itself as the delegate of the inherited navigationController and conforms to the UINavigationControllerDelegate protocol
-
Pawel Cala almost 9 yearsOverriding method navigationShouldPopOnBackButton in UIViewController doesn't work - Program executes parent method not the overriden one. Any solution for that? Does anyone have same issue?
-
testing almost 9 yearsDIdn't worked for me. In
viewWillDisappear
the current view controller is always removed from the navigation stack! -
user2526811 over 8 yearsgetting issue with iOS 7
-
Nick Weaver over 8 yearsIt's an old answer, but for those out there new to the topic, as of iOS 6 and later subclassing of UINaviationController is a viable option.
-
Pawriwes over 8 yearsits works like charm, but I could not convert into Swift version, can you please help me out.
-
Pawriwes over 8 yearsit goes all the way back to rootview if return true
-
kgaidis over 8 years@Pawriwes Here's a solution I wrote up that seems to work for me: stackoverflow.com/a/34343418/826435
-
pronebird over 8 yearsThis has issues with interactive pop gesture.
-
Frederik Winkelsdorf over 8 yearsI agree with Andy, this solution overwrites the UINavigationControllers own shouldPopItem method and breaks e.g. with NavigationControllers used in SplitViewController.
-
Manan Devani about 8 yearsThis helped me in another way. I return No every time. Add my back arrow button and used popToRootViewController method.
-
Turvy over 7 yearsMaybe I missed something, but it does'nt work for me, the method performSomeActionOnThePressOfABackButton of the extension is never called
-
kgaidis over 7 years@FlorentBreton maybe a misunderstanding?
shouldPopOnBackButtonPress
should get called as long as there are no bugs.performSomeActionOnThePressOfABackButton
is just a made-up method that does not exist. -
Turvy over 7 yearsI understood it, that's why I created a method
performSomeActionOnThePressOfABackButton
in my controller to execute a specific action when the back button is pressed, but this method was never called, the action is a normal back return -
Tim Autin over 7 yearsNot working for me neither. The shouldPop method is never called. Did you set a delegate somewhere?
-
kgaidis over 7 years@TimAutin I just tested this again and seems like something has changed. Key part to understand that in a
UINavigationController
, thenavigationBar.delegate
is set to the navigation controller. So the methods SHOULD get called. However, in Swift, I can't get them to be called, even in a subclass. I did, however, get them to be called in Objective-C, so I would just use the Objective-C version for now. Might be a Swift bug. -
Murray Sagal about 7 yearsAs of iOS 10, and perhaps earlier, this is no longer working.
-
Murray Sagal about 7 yearsCould you explain further how this proves the back button was tapped?
-
Chris Prince almost 7 yearsWorking for me from Swift 3, iOS 10.3.
-
CMont over 6 yearsThis is simple, but it works only if you are sure to come back to that view from any child view you may load. If the child skips this view as it goes back to the parent, your code will not be called (the view had already disappeared without having moved from the parent). But that's the same issue with only handling events on trigger of the Back button as asked by the OP. So this is a simple answer to his question.
-
MarkAllen4512 over 6 yearsI am getting an error:
Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton'
when I try to compile your code, for the lineif vc.responds(to: #selector(v...
Also, theself.topViewController
returns an optional and there is a warning for that also. -
MarkAllen4512 over 6 yearsFWIW, I have fixed that code by making:
let vc = self.topViewController as! MyViewController
and it seem to work fine so far. If you believe that is a right change, you could edit the code. Also, if you feel that it should not be done, I will be glad to know why. Thanks for this code. You should probably write a blog post about this, as this answer is buried down as per the votes. -
Lawliet over 6 years@SankarP The reason you got that error is your
MyViewController
may not conform toBackButtonDelegate
. Rather than forcing unwrap, you should doguard let vc = self.topViewController as? MyViewController else { return true }
to avoid possible crash. -
MarkAllen4512 over 6 yearsThanks. I think the guard statement should become:
guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }
to make sure that the screen is moving to the right page in case it cannot be rightly cast. I understand now that thenavigationBar
function is called in all VCs and not just the viewcontroller where this code is existing. May be it will be good to update the code in your answer too ? Thanks. -
Kachi over 6 yearsThis is one of my favorite StackOverflow answers of all-time!! Life-saver!
-
Kevin LeStarge almost 5 years@FlorentBreton I also must have missed something. Nothing happens for me either.
-
H4Hugo over 4 yearsCan you provide details about what's going on with the if viewControllers.count < navigationBar.items!.count { return true } check please?
-
Siempay over 4 years// Prevents from a synchronization issue of popping too many navigation items // and not enough view controllers or viceversa from unusual tapping
-
Phontaine Judd over 4 yearsThis is super simple and elegant. I love it. Just one problem: this will also fire if user swipes to go back, even if they cancel midway through. Perhaps a better solution would be to put this code into
viewDidDisappear
. That way it will only fire once view is definitely gone.