18

In my UIViewController I have a UINavigationController with a default back button. When the user clicks the back button, a warning message should appear: "Do you really want to go back?". I know, that it is not possible to trap the back button event. It's only possible the use viewWillDisappear and set a flag:

- (void)viewWillDisappear:(BOOL)animated {
    if (backBtnPressed) {
        UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Question" message:@"Do you really want to go back?" delegate:self cancelButtonTitle:@"No" otherButtonTitles: @"Yes", nil] autorelease];
        [alert show];   
    }
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 0) {
        // don't go back!
        // cancel the back button event
    }
    else if (buttonIndex == 1) {
        // go back
    }
}

But with this code I have no chance! I can't stop the back button event, isn't it?

Do I have to write my own back button and set it as leftBarButtonItem? Or is there anybody with a great idea? :-)

Thanks for your help!

Manni
  • 11,108
  • 15
  • 49
  • 67
  • All info on this page is out of date and not working as of Oct 2021. See accepted answer here https://stackoverflow.com/questions/43584340/swift-prevent-back-event-in-uiviewcontroller – Demian Turner Oct 26 '21 at 12:20

8 Answers8

56

My answer from another thread matches this question. So I repost it here:

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 controller
}

Download sample app.

Community
  • 1
  • 1
onegray
  • 5,105
  • 1
  • 23
  • 29
  • 9
    Sadly, we're now at iOS 8, and this brilliant code is STILL needed, to intercept the Back button action. Excellent solution, shame on Apple for not providing such functionality out of the box. – Mike Gledhill Feb 20 '15 at 09:34
  • Isn't this private API? Did you manage to publish the app on the store? – Cosmin Jul 31 '15 at 14:27
  • Where is there private API usage? – Chris Prince Oct 11 '16 at 22:58
  • This answer is one peace of the art! – Delorean Jul 27 '17 at 21:41
  • 5
    This extension works perfectly with the Swift 4. Thank You very much. [here is the swift 3 version of the extention](https://gist.github.com/HamGuy/a099058e674b573ffe433132f7b5651e), if someone need – udi Nov 13 '17 at 04:55
14

What you need to do is to use the delegate of the navigation bar, and not the navigation controller.

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPushItem:(UINavigationItem *)item; // called to push. return NO not to.
- (void)navigationBar:(UINavigationBar *)navigationBar didPushItem:(UINavigationItem *)item;    // called at end of animation of push or immediately if not animated
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;  // same as push methods
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item;
Enzo Tran
  • 5,750
  • 6
  • 31
  • 36
  • 8
    My navigation bar is managed by the navigation controller, and manually setting the delegate of the bar to be my view controller results in an exception that explains manually setting the delegate on the nav bar is not allowed if the bar is managed by a nav controller. – Manni Mar 08 '12 at 10:24
  • 1
    Just subclass UINavigationController, then override - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item; – Enzo Tran Mar 08 '12 at 21:30
  • Enzo, subclassing UINavigationController isn't allowed by Apple: http://stackoverflow.com/questions/1937616/why-doesnt-apple-allow-subclassing-of-uinavigationcontroller-and-what-are-my-a – Manni Mar 09 '12 at 09:13
  • The documentation says it is "not intended for subclassing", but it doesn't forbid you to do so. I have been subclassing UINavigationController in plenty of my apps without problem, and so do many other people. – Enzo Tran Mar 10 '12 at 07:58
  • Apparently the UINavigationBarDelegate methods are private, so that if I try to call [super navigationBar:navigationBar shouldPopItem:item] to get the default behaviour, there's a compile error "no visible @interface for 'UINavigationController' declares the selector 'navigationBar:shouldPopItem:'". Is there any legit way around this? – c roald Nov 13 '12 at 20:12
  • Delegate methods are used to *inform* you when an event occurs. They don't have any "default behavior", it's up to you to implement what you want. – Enzo Tran Nov 14 '12 at 12:27
  • What you suggest would be fine if you are not using a UINavigationController. However, if you are using a UINavigationController with iOS 6.1, you cannot set the delegate property of the UINavigationBar, as explained in the documentation. – Greg Glockner Apr 19 '13 at 20:31
  • This answer was in 2011, and it worked back then. Maybe Apple has changed something, I haven't used UINavigationController in a long time. – Enzo Tran Jun 11 '13 at 13:36
10

viewWillDisappear is a delegate method for the event that the view is going to disappear - and there's nothing the developer can do about that! If you could, it would be a viewShouldDisappear delegate method.

So I guess the only way is as you suggest, to use a custom leftBarButtonItem.

James Bedford
  • 28,702
  • 8
  • 57
  • 64
7

I must say this is one of the common use cases that Apple doesn't seem to make easy, and I see a lot of effort trying to get this working. I thought maybe I should summarize my findings here.

As many have pointed out, the method below in UINavigationBarDelegate is key to implementing this feature.

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;

Many have subclassed UINavigationController and implemented the method above to make it easy to use without direct access to the UINavigationBar.

Unfortunately, there still remain some issues.

  • The swipe back gesture does not invoke this method.
  • Although it seems necessary, crashes are reported calling popViewControllerAnimated: in this method.
  • The Back button remains grayed out, when pop is cancelled.

Swipe back gesture

We need to intercept the gesture by setting the delegate as is done in https://stackoverflow.com/a/23173035/2400328 .

If the UINavigationController is subclassed, that would be:

self.interactivePopGestureRecognizer.delegate = self

and implementing:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

Take care in when you modify the delegate property, as it gets modified after the initializer is called.

Not calling popViewControllerAnimated:

Although undocumented, calling popViewControllerAnimated: can be avoided as in https://stackoverflow.com/a/26084150/2400328.

It involves calling navigationBar:shouldPopItem: of UINavigationController (from the subclass).

The Back button

Although this may be a minor detail (especially, if you have designed your own Back button), there is a simple solution (written by me :) https://stackoverflow.com/a/29440633/2400328

You only need to set a property YES and NO.

auto item = navigationBar.topItem;
item.hidesBackButton = YES;
item.hidesBackButton = NO;
Community
  • 1
  • 1
techniao
  • 151
  • 1
  • 9
2

You can use a custom button with a graphics, which looks exactly like "Back" button and create a custom leftBarButtonItem view as UIButton with this graphics. Add target self to your button with custom back: selector and pop your alert there. If the user presses "yes" to quit dismiss this view controller, if not - do nothing. The trick here is the button which looks exactly as navigation bar's back button.

Nava Carmon
  • 4,523
  • 3
  • 40
  • 74
1

If you're looking for a way to do this in Swift on iOS 10, you can create a custom UINavigationController and then a UINavigationBarDelegate extension:

class MyNavigationController : UINavigationController {

}
extension MyNavigationController : UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        return false
    }
}
Max
  • 6,901
  • 7
  • 46
  • 61
  • Doesn't work as expected on iOS 14.2 beta. Breaks apart. I suppose we have to call super implementation somehow. – pronebird Oct 27 '20 at 12:39
1

Its better if u make your own back button and make it the left button of the Navigation controller. That can definitely help u to perform any action

visakh7
  • 26,380
  • 8
  • 55
  • 69
0

The Method

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;

is doing what you want. Unfortunately we are not supposed to delegate UINavigationBar to our own objects :(

The Apple Documentation states :

... In addition, a navigation controller object automatically assigns itself as the delegate of its UINavigationBar object and prevents other objects from changing that relationship. ...

One/The? way to do what you want is to put in your own back-button. In that Method you do your tests and call

[self.navigationController popViewControllerAnimated:true];

if the user is allowed to go back.

Thorsten Niehues
  • 13,712
  • 22
  • 78
  • 113