Changing back button in iOS 7 disables swipe to na

2019-01-03 20:36发布

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.

14条回答
狗以群分
2楼-- · 2019-01-03 21:03

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
查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-01-03 21:05

Try self.navigationController.interactivePopGestureRecognizer.enabled = YES;

查看更多
Melony?
4楼-- · 2019-01-03 21:05

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.

查看更多
Animai°情兽
5楼-- · 2019-01-03 21:07

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;
}
查看更多
时光不老,我们不散
6楼-- · 2019-01-03 21:10

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];
查看更多
男人必须洒脱
7楼-- · 2019-01-03 21:11

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
}}
查看更多
登录 后发表回答