6

I have a view with a back button managed with a navigation controller and I want to check if a file has been saved when the user click on the back button. If the file has been saved you go back in the previous view, else a uialertview ask you if you want to save the file or not.

So I did that but the view disapear and the alertview appear after.

-(void)viewWillDisappear:(BOOL)animated {
if(!self.fileSaved){
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"Save the file?"  delegate:self cancelButtonTitle:@"No" otherButtonTitles:@"Yes",nil];
    [alert show];
    [alert release];
}
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
switch (buttonIndex) {
    case 0:
        NSLog(@"NO");
        break;
    case 1:
        NSLog(@"yes");
        break;
    default:
        break;
}
}
Mathieu
  • 1,175
  • 4
  • 19
  • 34

4 Answers4

6

When viewWillDisappear is called, it's already too late. You should intercept the back button earlier on. I have never done it, but my suggestion is to set the delegate on the navigationBar property in your viewDidAppear method:

// save the previous delegate (create an ivar for that)
prevNavigationBarDelegate = self.navigationController.navigationBar.delegate;

self.navigationController.navigationBar.delegate = self;

Don't forget to set it back in viewWillDisappear:

self.navigationController.navigationBar.delegate = prevNavigationBarDelegate;

Then intercept the shouldPopItem method:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
     if(!self.fileSaved) {
         UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"Save the file?"  delegate:self cancelButtonTitle:@"No" otherButtonTitles:@"Yes",nil];
         [alert show];
         [alert release];

         return NO;
     }

   if ([prevNavigationBarDelegate respondsToSelector:@selector(navigationBar:shouldPopItem:)]) 
      return [prevNavigationBarDelegate navigationBar:navigationBar shouldPopItem:item];

   return YES; 
}

And in the YES handler for the dialog, manually pop the controller:

[self.navigationController popViewController:YES];
Philippe Leybaert
  • 168,566
  • 31
  • 210
  • 223
  • Sounds logical, but you should probably save the current navigation bar delegate before overwriting it, reset it after you've decided to pop yourself, and maybe even pass through the `navigationBar:shouldPopItem:` call to the old delegate (if not nil) before showing your alert. – pix0r Sep 08 '09 at 22:29
  • @Mathieu: Is the method shouldPopItem called? If not, you may have to set the delegate in the viewDidAppear method instead of the init method. pix0r's comment is also valid. I'll update my answer to reflect that. – Philippe Leybaert Sep 08 '09 at 22:37
  • The shouldPopItem is not called either if I set the delegate in the viewDidAppear or viewDidLoad or in the init method – Mathieu Sep 08 '09 at 22:47
  • 6
    There is a problem with this because you cannot change the delegate for a navigation bar that was "created by a navigation controller and is being managed by that object" (per Apple documentation). – yabada Nov 23 '10 at 19:15
  • It seems that normally the UINavigationController is the delegate of the UINavigationBar, so you can subclass UINavigationController and override this method there. In practice, I did get a warning when calling this method against super (warning re: super may not respond to this), but it did work. – DataGraham Oct 31 '12 at 17:53
  • As @yabada mentioned, there would be an exception of "Cannot manually set the delegate on a UINavigationBar managed by a controller" if trying to set UINavigationBar's delegate to the current controller in its `viewDidAppear` method. Personally I think the better solution for this, is to manually set the leftBarButtonItem to a custom BarButtonItem, to perform necessary process in its action. – BabyPanda May 10 '13 at 09:36
4

You must subclass UINavigationController for this to work. Then override - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item . You should set up a custom Delegate protocol that your view controllers adopt and, if you allow it to pop, call your [super navigationBar shouldPopItem:], else, return NO to the above method.

Rafael Nobre
  • 5,062
  • 40
  • 40
2

Wouldn't it be easier just to add a left button item as in the following:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(saveThisDate)];
self.navigationItem.leftBarButtonItem = backButton;
[backButton release];
Mutawe
  • 6,464
  • 3
  • 47
  • 90
John
  • 21
  • 1
  • I tried all the other complicated stuffs from other posts, but this one worked like a charm without the complications. – coder May 21 '12 at 05:19
  • Likewise. If you have a Navigation Controller in play, and want your action to be done for only a particular viewcontroller, I believe this is the only way. – Chris Mar 11 '14 at 17:06
0

To follow up on nobre response and as Jon mentionned it, the best way is to subclass UINavigationController.

The easiest way and fastest way to acheive this :

  1. Modify the class of your navigation controller in Interface Builder to inherit from CustomNavigationControllerDelegate

Custom navigation class

  1. Implement the CustomNavigationControllerDelegate protocol in your UIViewController

@interface YourViewController <CustomNavigationControllerDelegate>

#pragma mark - UINavigationBar Delegate Methods
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

    UIAlertView* alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:self cancelButtonTitle:cancel otherButtonTitles:ok, nil];
    alert.tag = kpopup_back;
    [alert show];

    return NO;
}
  1. Register your controller as the delegate

#pragma mark - viewWillAppear - (void) viewWillAppear:(BOOL)animated { ((CustomNavigationController*)self.navigationController).customDelegate = self; }

  1. Finally and important part, REMOVE the delegate (to avoid to re-trigger yourself on the pop) and pop the controller yourself in the the UIAlertViewDelegate

case kpopup_back : { if(buttonIndex != 0) //OK { ((CustomNavigationController*)self.navigationController).customDelegate = nil; [self.navigationController popViewControllerAnimated:YES]; } } break;

It works flawlessly on my side, hope it can help.


Here are the sources :

CustomNavigationControllerDelegate.h

#import <UIKit/UIKit.h>

@protocol CustomNavigationControllerDelegate <NSObject>
@optional
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
@end

@interface CustomNavigationController : UINavigationController

@property (nonatomic, retain) id<CustomNavigationControllerDelegate> customDelegate;

@end

CustomNavigationControllerDelegate.m

#import "CustomNavigationController.h"

@interface CustomNavigationController ()

@end

@implementation CustomNavigationController

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

    if (_customDelegate && [_customDelegate respondsToSelector:@selector(navigationBar:shouldPopItem:)]) {
        return [_customDelegate navigationBar:navigationBar shouldPopItem:item];
    }

    return YES;
}

@end
Damien Praca
  • 3,126
  • 21
  • 14