Sometimes you want to stop your user immediately going back to the previous screen without prompting them first. You could do this by creating a custom back button - drag a button in storyboard to the top left of the navigation bar, and wire it up to your view controller. In your custom back button's selector, show an alert asking the user to confirm. If confirmed, you then manually go back by calling
One problem with this is that you lose the special left-slanting arrow shape of the back button. Another problem is that other actions that pop the navigation controller will be excluded from this "Are You Really Sure?" check. For example, double-tapping on a tab on your tab bar controller by default pops you to the root view controller.
Hence the need to be able to intercept all attempts to pop the current view controller, and also to be able to prevent the pop from happening.
Turns out this is possible by writing a custom navigation controller. But it is a little tricky to get it right, so I'm putting the code here to save others some time.
Create a new class
The [self popViewControllerAnimated:YES].
One problem with this is that you lose the special left-slanting arrow shape of the back button. Another problem is that other actions that pop the navigation controller will be excluded from this "Are You Really Sure?" check. For example, double-tapping on a tab on your tab bar controller by default pops you to the root view controller.
Hence the need to be able to intercept all attempts to pop the current view controller, and also to be able to prevent the pop from happening.
Turns out this is possible by writing a custom navigation controller. But it is a little tricky to get it right, so I'm putting the code here to save others some time.
Safe Navigation Controller and Delegate
Create a new class
UISafeNavigationController
as follows://
// UISafeNavigationController.h
#import <UIKit/UIKit.h>
@protocol UISafeNavigationDelegate <NSObject>
@required
- (BOOL)navigationController:(UINavigationController *)navigationController
shouldPopViewController:(UIViewController *)controller pop:(void(^)())pop;
@end
@interface UISafeNavigationController : UINavigationController
@property (weak, nonatomic) id<UISafeNavigationDelegate> safeDelegate;
@end
//
// UISafeNavigationController.m
#import "UISafeNavigationController.h"
@implementation UISafeNavigationController
@end
safeDelegate
will be polled before every pop and given the opportunity to prevent the pop from happening. The pop:(void(^)())pop
is passed to the safeDelegate
to allow it to trigger the pop to happen at some later point, after it has done whatever checks it needs (e.g. get confirmation from the user via an alert).Now the main job is to override the pop methods in
UISafeNavigationController
. Add the following methods to the implementation in the .m file:- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
if (self.safeDelgate && ![self.safeDelegate navigationController:self
shouldPopViewController:[self.viewControllers lastObject]
pop:^{ [super popViewControllerAnimated:animated]; }])
{
return nil;
}
return [super popViewControllerAnimated:animated];
}
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
{
if (self.safeDelgate && ![self.safeDelegate navigationController:self
shouldPopViewController:[self.viewControllers lastObject]
pop:^{ [super popToRootViewControllerAnimated:animated]; }])
{
return nil;
}
return [super popToRootViewControllerAnimated:animated];
}
- (NSArray *)popToViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
if (self.safeDelgate && ![self.safeDelegate navigationController:self
shouldPopViewController:[self.viewControllers lastObject]
pop:^{ [super popToViewController:viewController animated:animated]; }])
{
return nil;
}
return [super popToViewController:viewController animated:animated];
}
With the above code, any attempts to pop a view controller will result in a call to the safeDelegate
(if it has been set) for confirmation.Now for the slightly tricky part. We need to stop the navigation bar popping as well - at the moment, it will still pop when you click back, even if we prevent the navigation controller from popping.
To achieve this, make the
UISafeNavigationController
conform to UINavigationBarDelegate
with the following changes in the header file:
@interface UISafeNavigationController : UINavigationController
<UINavigationBarDelegate>
@property (weak, nonatomic) id<UISafeNavigationDelegate> safeDelegate;
- (BOOL)navigationBar:(UINavigationBar *)navigationBar
shouldPopItem:(UINavigationItem *)item;
@end
Finally, implement the UINavigationBarDelegate
method as follows:- (BOOL)navigationBar:(UINavigationBar *)navigationBar
shouldPopItem:(UINavigationItem *)item
{
if (item == [[self.viewControllers lastObject] navigationItem]) {
[self popViewControllerAnimated:YES];
return NO;
}
return YES;
}
And that's all there is to it! Now all you need to do is change the class of your navigation controller in storyboard to UISafeNavigationController
, then for any view controllers that need to prevent the user going back, add UISafeNavigationDelegate
to their protocol list and add the following in the implementation:- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [((UISafeNavigationController *) self.navigationController) setSafeDelegate:self]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [((UISafeNavigationController *) self.navigationController) setSafeDelegate:nil]; } - (BOOL)navigationController:(UINavigationController *)navigationController shouldPopViewController:(UIViewController *)controller pop:(void(^)())pop { if (/* Some test for pop */) { pop(); return NO; } return YES; }
Like I say, this took me a bit of fiddling to get right, so maybe it'll save someone else the hassle!