18

I have a UITableViewController that launches a UIViewController and I would like to trap whenever the back button is pressed in the child controller, which is the class that derives from 'UIViewController'. I can change the Back Button title but setting the target & action values when setting the backBarButtonItem seems to get ignored. What's a way to receiving some kind of notification that the Back button was tapped?

- (void)showDetailView 
{
    // How I'm creating & showing the detail controller
    MyViewController *controller = [[MyViewController alloc] initWithNibName:@"MyDetailView" bundle:nil];   

    UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Pages"
                            style:UIBarButtonItemStyleBordered 
                            target:self                                     
                            action:@selector(handleBack:)];

    self.navigationItem.backBarButtonItem = backButton;
    [backButton release];

    [self.navigationController pushViewController:controller animated:animated];
    [controller release];

}   

- (void)handleBack:(id)sender
{
    // not reaching here
    NSLog(@"handleBack event reached");
}
CodeBender
  • 35,668
  • 12
  • 125
  • 132
Alexi Groove
  • 6,646
  • 12
  • 47
  • 54
  • 1
    For people looking for an efficient iOS 5+ solution: http://stackoverflow.com/a/13370744/1072846 – Eric Jan 29 '13 at 01:10

7 Answers7

19

You can implement the viewWillDisappear method of UIViewController. This gets called when your controller is about to go away (either because another one was pushed onto the navigation controller stack, or because the 'back' button was pressed).

To determine whether the view is disappearing because of the back button being pressed, you can use a custom flag that you set wherever you push a new controller onto the navigation controller, like shown below

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (viewPushed) {
        viewPushed = NO;   // Flag indicates that view disappeared because we pushed another controller onto the navigation controller, we acknowledge it here
    } else {
        // Here, you know that back button was pressed
    }   
}

And wherever you push a new view controller, you would have to remember to also set that flag...

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    ...
    viewPushed = YES;
    [self.navigationController pushViewController:myNewController animated:YES];
    ...
}
Zoran Simic
  • 10,293
  • 6
  • 33
  • 35
  • How can you know that the view didn't disappear because some other button invoked a child view? I want to know only when the "Back" button is pressed. – Alexi Groove Oct 15 '09 at 16:57
  • In cases like this, I set a flag in didSelectRowAtIndexPath: (where you usually push a new controller onto the navigation controller). viewWillDisappear can then inspect that flag and deduce whether the back button was pressed or not – Zoran Simic Nov 06 '09 at 10:43
  • That still doesn't address the problem Justin pointed out; With your flag you will know how the view came into existence, but that doesn't mean that's the only way it will disappear. – kbanman Jan 25 '10 at 07:08
  • It does, because you're in control of what gets pushed onto the navigation controller. If you do it right (ie, you don't forget any cases), the only case left where your flag isn't set and your view is disappearing is when the user pressed the back button. This works great for me. – Zoran Simic Jan 30 '10 at 07:43
  • depreceated this answer – Esqarrouth Feb 16 '14 at 13:34
  • In viewWillDisappear method, it can do, but not the elegant way, I think the question asker wants to find a accurate solution. – childrenOurFuture Jan 22 '16 at 03:46
17

It has been a while since this was asked, but I just tried to do this myself. I used a solution similar to Zoran's, however instead of using a flag I did this:

- (void)viewWillDisappear: (BOOL)animated
{
    [super viewWillDisappear: animated];
    if (![[self.navigationController viewControllers] containsObject: self])
    {
        // the view has been removed from the navigation stack, back is probably the cause
        // this will be slow with a large stack however.
    }
}

I think it bypasses the issues with flags and IMO is cleaner, however not as efficient (if there are lots of items on the navigation controller).

Andrew
  • 396
  • 3
  • 5
  • 6
    You might be able to use `[self.navigationController.viewControllers indexOfObjectIdenticalTo: self] != NSNotFound` instead of `containsObject`, which would only compare the pointer address. – Steven Fisher Jun 12 '12 at 20:51
  • 1
    If your controller is contained within another container, you'll have to compare via: "indexOfObjectIdenticalTo:self.parentViewController", other than that, works. – marmor Apr 17 '13 at 08:15
  • if (([self.parentViewController.navigationController.viewControllers indexOfObjectIdenticalTo: self] != NSNotFound)) worked for me. – SmileBot Jul 29 '13 at 20:37
  • 1
    It is plenty efficient. This isn't code that needs to be optimized. – Dustin Jan 02 '15 at 20:57
14

In my opinion the best solution.

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (![parent isEqual:self.parentViewController]) {
         NSLog(@"Back pressed");
    }
}

But it only works with iOS5+

Blank
  • 4,872
  • 2
  • 28
  • 25
9

I use this code:

- (void) viewWillDisappear:(BOOL)animated {

   if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound)
   {
      // your view controller already out of the stack, it meens user pressed Back button
   }
}

But this is not actual when user presses tab bar button and pops to root view controller at one step. For this situation use this:

   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(viewControllerChange:)
                                                name:@"UINavigationControllerWillShowViewControllerNotification"
                                              object:self.navigationController];


- (void) viewControllerChange:(NSNotification*)notification {

   NSDictionary* userInfo = [notification userInfo];

   if ([[userInfo objectForKey:@"UINavigationControllerNextVisibleViewController"] isKindOfClass:[<YourRootControllerClass> class]])
   { 
      // do your staff here
   }
}

Don't forget then:

   [[NSNotificationCenter defaultCenter] removeObserver:self
                                                   name:@"UINavigationControllerWillShowViewControllerNotification"
                                                 object:self.navigationController];
m8labs
  • 3,671
  • 2
  • 30
  • 32
1
{
    UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"back"
                                                                   style:UIBarButtonItemStyleBordered 
                                                                  target:self                                                                             
                                                                  action:@selector(handleBack:)];
    self.navigationItem.leftBarButtonItem = backButton;
    [backButton release];
    [self filldata];
    [super viewDidLoad];
}

just replace backBarButtonItem with leftBarButtonItem

Abdullah Umer
  • 4,234
  • 5
  • 36
  • 65
  • This seems like the best answer to me. Why doesn't this answer have more upvotes? Am I overlooking something? – daniel Sep 29 '22 at 03:55
1

You can make your own button and place it as the leftBarButtonItem. Then have it call your method where you can do whatever, and call [self.navigationController popViewController... yourself

coneybeare
  • 33,113
  • 21
  • 131
  • 183
  • 1
    the 'leftBarButtonItem' won't show the same button style with the sideways looking triangle. Instead I think the button will be square. Is there a way to make it look like the typical backbutton as well as trap the event? – Alexi Groove Oct 12 '09 at 23:07
  • You can create your own left arrow image if you know the width of your button text -- or you could create it programmatically, mimicking what Apple does. – coneybeare Oct 12 '09 at 23:32
-2

Simply use viewDidDisappear instead. It will be perfectly called in any scenario.

We are basing your lifecycle management on viewDidAppear and viewDidDisappear. If you know Android: the both are comparable to onResume and onPause methods. But there is a difference when it comes to locking the screen or pressing the homebutton on iOS.