I have an two controllers 1st is self and 2nd is maincontroller, where I'm pushing maincontroller in stack, so the back button is automatically coming.
Here I need to make an alert when the user presses the back button.
How can I do this?
Or you can use the UINavigationController's delegate methods. The method willShowViewController is called when the back button of your VC is pressed.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
First hide the back button by using
self.navigationItem.hidesBackButton = YES;
and then create your own Custom Button:
UIBarButtonItem *backBtn =[[UIBarButtonItem alloc]initWithTitle:#"back" style:UIBarButtonItemStyleDone target:self action:#selector(popAlertAction:)];
self.navigationItem.leftBarButtonItem=backBtn;
[backBtn release];
and your selector is here:
- (void)popAlertAction:(UIBarButtonItem*)sender
{
//Do ur stuff for pop up
}
Best and Easiest way
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];
}
Create your own UIBarButtonItem and set it as the leftBarButtonItem in viewDidLoad method of mainController.
For example (here I used a system item but you can also create a different one, see class reference for details).
UIBarButtonItem *leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(showAlertView:)];
self.navigationItem.leftBarButtonItem = leftBarButtonItem;
// only if you don't use ARC
// [leftBarButtonItem release];
where
- (void)showAlertView:(id)sender
{
// alert view here...
}
add a custom back button with an action and set your alert in that action method.You can add your custom back button from here: http://www.applausible.com/blog/?p=401
viewControllerCount - is the var that holds the number of viewControllers previously was in the UINavigationController. Then, we check if viewControllerCount > [viewControllers count] if so, we know that we will get back (i.e. Back button imitation).
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
NSArray *viewControllers = [navigationController viewControllers];
if (viewControllerCount > [viewControllers count])
{
// your code
}
viewControllerCount = [viewControllers count];
}
extension ViewController: UINavigationControllerDelegate {
// when the self != viewcontroller ,it's mean back
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if self != viewController {
// your code
}
}}
create a button and give the button action as follows.
[self alert];
and when the alert is displayed, after tapping over yes
[self.navigationController popViewController];
after this,
self.navigationController.LeftBarButton = myButton;
this may help
Related
I want to pop to specific class in my app.
It works fine in iOS 8 but
Problem
in iOS 7: it gives me error like "Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted"
My code is as follow
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[MyAccountVC class]])
{
[self.navigationController popToViewController:controller animated:YES];
break;
}
}
Point 1: viewWillDisapear is called before the view has popped from the navigation stack. Writing another popToViewController method inside this will cause a transition clash as both the transitions are animated:YES.
Point 2: The back button action of the UINavigationController can be modified by implementing the custom back button as shown in code here:
- (void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem *navigationBarbackButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(backToView:)]; // you can also use a custom image for back button
self.navigationItem.backBarButtonItem = navigationBarbackButton;
}
- (IBAction)backToView:(id)sender
{
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[MyAccountVC class]])
{
[self.navigationController popToViewController:controller animated:YES];
break;
}
}
}
I have an iOS 7 app where I am setting a custom back button like this:
UIImage *backButtonImage = [UIImage imageNamed:#"back-button"];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[backButton setImage:backButtonImage forState:UIControlStateNormal];
backButton.frame = CGRectMake(0, 0, 20, 20);
[backButton addTarget:self
action:#selector(popViewController)
forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
viewController.navigationItem.leftBarButtonItem = backBarButtonItem;
But this disables the iOS 7 "swipe left to right" gesture to navigate to the previous controller. Does anyone know how I can set a custom button and still keep this gesture enabled?
EDIT:
I tried to set the viewController.navigationItem.backBarButtonItem instead, but this doesn't seem to show my custom image.
IMPORTANT:
This is a hack. I would recommend taking a look at this answer.
Calling the following line after assigning the leftBarButtonItem worked for me:
self.navigationController.interactivePopGestureRecognizer.delegate = self;
Edit:
This does not work if called in init methods. It should be called in viewDidLoad or similar methods.
Use the backIndicatorImage and backIndicatorTransitionMaskImage properties of the UINavigationBar if at all possible. Setting these on an a UIAppearanceProxy can easily modify behavior across your application. The wrinkle is that you can only set those on ios 7, but that works out because you can only use the pop gesture on ios 7 anyway. Your normal ios 6 styling can remain intact.
UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:#selector(setBackIndicatorImage:)])
{
appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:#"back"];
appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
//sets back button color
appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
//do ios 6 customization
}
Trying to manipulate the interactivePopGestureRecognizer's delegate will lead to a lot of issues.
I saw this solution http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/ which subclasses UINavigationController. Its a better solution as it handles the case where you swipe before the controller is in place - which causes a crash.
In addition to this I noticed if you do a swipe on the root view controller (after pushing on one, and back again) the UI becomes unresponsive (also same problem in answer above).
So the code in the subclassed UINavigationController should look like so:
#implementation NavigationController
- (void)viewDidLoad {
[super viewDidLoad];
__weak NavigationController *weakSelf = self;
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Hijack the push method to disable the gesture
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.enabled = NO;
}
[super pushViewController:viewController animated:animated];
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate {
// Enable the gesture again once the new controller is shown
self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:#selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}
#end
I use
[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:#"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:#"nav_back.png"]];
[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];
Here is swift3 version of Nick H247's answer
class NavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
interactivePopGestureRecognizer?.delegate = self
delegate = self
}
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
interactivePopGestureRecognizer?.isEnabled = false
}
super.pushViewController(viewController, animated: animated)
}
}
extension NavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
}
}
extension NavigationController: UIGestureRecognizerDelegate {}
I also hide the back button, replacing it with a custom leftBarItem.
Removing interactivePopGestureRecognizer delegate after push action worked for me:
[self.navigationController pushViewController:vcToPush animated:YES];
// Enabling iOS 7 screen-edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
This is from http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks, but it causes several bugs:
Push another viewController into the navigationController when swiping in from the left edge of the screen;
Or, swipe in from the left edge of the screen when the topViewController is popping up from the navigationController;
e.g. When the rootViewController of navigationController is showing, swipe in from the left edge of the screen, and tap something(QUICKLY) to push anotherViewController into the navigationController, then
The rootViewController does not respond any touch event;
The anotherViewController will not be shown;
Swipe from the edge of the screen again, the anotherViewController will be shown;
Tap the custom back button to pop the anotherViewController, crash!
So you must implement UIGestureRecognizerDelegate method in self.navigationController.interactivePopGestureRecognizer.delegate like this:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
}
return YES;
}
Try self.navigationController.interactivePopGestureRecognizer.enabled = YES;
I did not write this, but the following blog helped a lot and solved my issues with custom navigation button:
http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/
In summary, he implements a custom UINavigationController that uses the pop gesture delegate. Very clean and portable!
Code:
#interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
#end
#implementation CBNavigationController
- (void)viewDidLoad
{
__weak CBNavigationController *weakSelf = self;
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
// Hijack the push method to disable the gesture
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
self.interactivePopGestureRecognizer.enabled = NO;
[super pushViewController:viewController animated:animated];
}
#pragma mark UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
// Enable the gesture again once the new controller is shown
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
self.interactivePopGestureRecognizer.enabled = YES;
}
Edit. Added fix for problems when a user tries to swipe left on a root view controller:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)] &&
self.topViewController == [self.viewControllers firstObject] &&
gestureRecognizer == self.interactivePopGestureRecognizer) {
return NO;
}
return YES;
}
RootView
override func viewDidAppear(_ animated: Bool) {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
ChildView
override func viewDidLoad() {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
extension ChildViewController: UIGestureRecognizerDelegate {}
Use this logic to keep enable or disable the swipe gesture..
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
if (self.navigationController.viewControllers.count > 1)
{
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
else
{
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
}
I had a similar problem where I was assigning the current view controller as the delegate for the interactive pop gesture, but would break the gesture on any views pushed, or views underneath the view in the nav stack. The way I solved this was to set the delegate in -viewDidAppear, then set it to nil in -viewWillDisappear. That allowed my other views to work correctly.
Imagine we are using Apple's default master/detail project template, where master is a table view controller and tapping on it will show the detail view controller.
We want to customize the back button that appears in the detail view controller. This is how to customize the image, image color, text, text color, and font of the back button.
To change the image, image color, text color, or font globally, place the following in a location that is called before any of your view controllers are created (e.g. application:didFinishLaunchingWithOptions: is a good place).
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];
// change the back button, using default tint color
navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:#"back"];
navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
// change the back button, using the color inside the original image
navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:#"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
// change the tint color of everything in a navigation bar
navigationBarAppearance.tintColor = [UIColor greenColor];
// change the font in all toolbar buttons
NSDictionary *barButtonTitleTextAttributes =
#{
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0],
NSForegroundColorAttributeName: [UIColor purpleColor]
};
[[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
return YES;
}
Note, you can use appearanceWhenContainedIn: to have more control over which view controllers are affected by these changes, but keep in mind that you can't pass [DetailViewController class], because it is contained inside a UINavigationController, not your DetailViewController. This means you will need to subclass UINavigationController if you want more control over what is affected.
To customize the text or the font/color of a specific back button item, you must do so in the MasterViewController (not the DetailViewController!). This seems unintuitive because the button appears on the DetailViewController. However once you understand that the way to customize it is by setting a property on a navigationItem, it begins to make more sense.
- (void)viewDidLoad { // MASTER view controller
[super viewDidLoad];
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:#"Testing"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
NSDictionary *barButtonTitleTextAttributes =
#{
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0],
NSForegroundColorAttributeName: [UIColor purpleColor]
};
[buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
self.navigationItem.backBarButtonItem = buttonItem;
}
Note: attempting to set the titleTextAttributes after setting self.navigationItem.backBarButtonItem doesn't seem to work, so they must be set before you assign the value to this property.
Create a class 'TTNavigationViewController' which is subclass of 'UINavigationController' and make your existing navigation controller of this class either in storyboard/class, Example code in class -
class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.setNavigationBarHidden(true, animated: false)
// enable slide-back
if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
self.interactivePopGestureRecognizer?.isEnabled = true
self.interactivePopGestureRecognizer?.delegate = self
}
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}}
I have an iOS 7 app where I am setting a custom back button like this:
UIImage *backButtonImage = [UIImage imageNamed:#"back-button"];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[backButton setImage:backButtonImage forState:UIControlStateNormal];
backButton.frame = CGRectMake(0, 0, 20, 20);
[backButton addTarget:self
action:#selector(popViewController)
forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
viewController.navigationItem.leftBarButtonItem = backBarButtonItem;
But this disables the iOS 7 "swipe left to right" gesture to navigate to the previous controller. Does anyone know how I can set a custom button and still keep this gesture enabled?
EDIT:
I tried to set the viewController.navigationItem.backBarButtonItem instead, but this doesn't seem to show my custom image.
IMPORTANT:
This is a hack. I would recommend taking a look at this answer.
Calling the following line after assigning the leftBarButtonItem worked for me:
self.navigationController.interactivePopGestureRecognizer.delegate = self;
Edit:
This does not work if called in init methods. It should be called in viewDidLoad or similar methods.
Use the backIndicatorImage and backIndicatorTransitionMaskImage properties of the UINavigationBar if at all possible. Setting these on an a UIAppearanceProxy can easily modify behavior across your application. The wrinkle is that you can only set those on ios 7, but that works out because you can only use the pop gesture on ios 7 anyway. Your normal ios 6 styling can remain intact.
UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:#selector(setBackIndicatorImage:)])
{
appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:#"back"];
appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
//sets back button color
appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
//do ios 6 customization
}
Trying to manipulate the interactivePopGestureRecognizer's delegate will lead to a lot of issues.
I saw this solution http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/ which subclasses UINavigationController. Its a better solution as it handles the case where you swipe before the controller is in place - which causes a crash.
In addition to this I noticed if you do a swipe on the root view controller (after pushing on one, and back again) the UI becomes unresponsive (also same problem in answer above).
So the code in the subclassed UINavigationController should look like so:
#implementation NavigationController
- (void)viewDidLoad {
[super viewDidLoad];
__weak NavigationController *weakSelf = self;
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Hijack the push method to disable the gesture
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.enabled = NO;
}
[super pushViewController:viewController animated:animated];
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate {
// Enable the gesture again once the new controller is shown
self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:#selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}
#end
I use
[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:#"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:#"nav_back.png"]];
[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];
Here is swift3 version of Nick H247's answer
class NavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
interactivePopGestureRecognizer?.delegate = self
delegate = self
}
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
interactivePopGestureRecognizer?.isEnabled = false
}
super.pushViewController(viewController, animated: animated)
}
}
extension NavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
}
}
extension NavigationController: UIGestureRecognizerDelegate {}
I also hide the back button, replacing it with a custom leftBarItem.
Removing interactivePopGestureRecognizer delegate after push action worked for me:
[self.navigationController pushViewController:vcToPush animated:YES];
// Enabling iOS 7 screen-edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
This is from http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks, but it causes several bugs:
Push another viewController into the navigationController when swiping in from the left edge of the screen;
Or, swipe in from the left edge of the screen when the topViewController is popping up from the navigationController;
e.g. When the rootViewController of navigationController is showing, swipe in from the left edge of the screen, and tap something(QUICKLY) to push anotherViewController into the navigationController, then
The rootViewController does not respond any touch event;
The anotherViewController will not be shown;
Swipe from the edge of the screen again, the anotherViewController will be shown;
Tap the custom back button to pop the anotherViewController, crash!
So you must implement UIGestureRecognizerDelegate method in self.navigationController.interactivePopGestureRecognizer.delegate like this:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
}
return YES;
}
Try self.navigationController.interactivePopGestureRecognizer.enabled = YES;
I did not write this, but the following blog helped a lot and solved my issues with custom navigation button:
http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/
In summary, he implements a custom UINavigationController that uses the pop gesture delegate. Very clean and portable!
Code:
#interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
#end
#implementation CBNavigationController
- (void)viewDidLoad
{
__weak CBNavigationController *weakSelf = self;
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
// Hijack the push method to disable the gesture
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
self.interactivePopGestureRecognizer.enabled = NO;
[super pushViewController:viewController animated:animated];
}
#pragma mark UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
// Enable the gesture again once the new controller is shown
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
self.interactivePopGestureRecognizer.enabled = YES;
}
Edit. Added fix for problems when a user tries to swipe left on a root view controller:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)] &&
self.topViewController == [self.viewControllers firstObject] &&
gestureRecognizer == self.interactivePopGestureRecognizer) {
return NO;
}
return YES;
}
RootView
override func viewDidAppear(_ animated: Bool) {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
ChildView
override func viewDidLoad() {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
extension ChildViewController: UIGestureRecognizerDelegate {}
Use this logic to keep enable or disable the swipe gesture..
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
if (self.navigationController.viewControllers.count > 1)
{
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
else
{
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
}
I had a similar problem where I was assigning the current view controller as the delegate for the interactive pop gesture, but would break the gesture on any views pushed, or views underneath the view in the nav stack. The way I solved this was to set the delegate in -viewDidAppear, then set it to nil in -viewWillDisappear. That allowed my other views to work correctly.
Imagine we are using Apple's default master/detail project template, where master is a table view controller and tapping on it will show the detail view controller.
We want to customize the back button that appears in the detail view controller. This is how to customize the image, image color, text, text color, and font of the back button.
To change the image, image color, text color, or font globally, place the following in a location that is called before any of your view controllers are created (e.g. application:didFinishLaunchingWithOptions: is a good place).
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];
// change the back button, using default tint color
navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:#"back"];
navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
// change the back button, using the color inside the original image
navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:#"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
// change the tint color of everything in a navigation bar
navigationBarAppearance.tintColor = [UIColor greenColor];
// change the font in all toolbar buttons
NSDictionary *barButtonTitleTextAttributes =
#{
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0],
NSForegroundColorAttributeName: [UIColor purpleColor]
};
[[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
return YES;
}
Note, you can use appearanceWhenContainedIn: to have more control over which view controllers are affected by these changes, but keep in mind that you can't pass [DetailViewController class], because it is contained inside a UINavigationController, not your DetailViewController. This means you will need to subclass UINavigationController if you want more control over what is affected.
To customize the text or the font/color of a specific back button item, you must do so in the MasterViewController (not the DetailViewController!). This seems unintuitive because the button appears on the DetailViewController. However once you understand that the way to customize it is by setting a property on a navigationItem, it begins to make more sense.
- (void)viewDidLoad { // MASTER view controller
[super viewDidLoad];
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:#"Testing"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
NSDictionary *barButtonTitleTextAttributes =
#{
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0],
NSForegroundColorAttributeName: [UIColor purpleColor]
};
[buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
self.navigationItem.backBarButtonItem = buttonItem;
}
Note: attempting to set the titleTextAttributes after setting self.navigationItem.backBarButtonItem doesn't seem to work, so they must be set before you assign the value to this property.
Create a class 'TTNavigationViewController' which is subclass of 'UINavigationController' and make your existing navigation controller of this class either in storyboard/class, Example code in class -
class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.setNavigationBarHidden(true, animated: false)
// enable slide-back
if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
self.interactivePopGestureRecognizer?.isEnabled = true
self.interactivePopGestureRecognizer?.delegate = self
}
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}}
I want to navigate to the particular page in my application and i also dont want to create any custom back button for that.If I can override the method of the navigation bar back button so I can call the poptorootviewcontroller.so i can go to specific page. Anyone knows what is the method that is called by the navigation bar button and if we can use it?
You can try this.. Write your logic in this native method.
-(void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
// Your Code
}
You will have to provide the name and the implementation for the button method As there is no standard method ..
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(backButtonPressed)] autorelease];
implementation ..
-(void) backButtonPressed {
NSLog(#"Back button presses");
}
Try to use the below code:
NSArray * viewController = self.navigationController.viewControllers;
if([viewController count] > 3)
{
UIViewController * vc = [viewController objectAtIndex:0];
[self.navigationController popToViewController:vc animated:YES];
}
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");
}
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];
...
}
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).
In my opinion the best solution.
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(#"Back pressed");
}
}
But it only works with iOS5+
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];
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
{
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
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.