81

我有一个 iOS 7 应用程序,我在其中设置了一个自定义后退按钮,如下所示:

    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;

但这会禁用 iOS 7 的“从左向右滑动”手势导航到上一个控制器。有谁知道我如何设置自定义按钮并仍然启用此手势?

编辑:我尝试设置 viewController.navigationItem.backBarButtonItem ,但这似乎没有显示我的自定义图像。

4

14 回答 14

83

重要提示: 这是一个黑客。我建议看看这个答案

leftBarButtonItem在为我分配工作后调用以下行:

self.navigationController.interactivePopGestureRecognizer.delegate = self;

编辑:init如果在方法中 调用,这不起作用。它应该被调用viewDidLoad或类似的方法。

于 2013-09-29T09:14:22.593 回答
56

如果可能,请使用 UINavigationBar 的 backIndicatorImage 和 backIndicatorTransitionMaskImage 属性。在 UIAppearanceProxy 上设置这些可以轻松修改整个应用程序的行为。皱纹是你只能在 ios 7 上设置这些,但因为你只能在 ios 7 上使用 pop 手势,所以这很有效。您的正常 ios 6 样式可以保持不变。

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
}

试图操纵 interactivePopGestureRecognizer 的委托会导致很多问题。

于 2013-12-02T14:27:26.430 回答
29

我看到了这个解决方案http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/它是 UINavigationController 的子类。它是一个更好的解决方案,因为它可以处理您在控制器就位之前滑动的情况 - 这会导致崩溃。

除此之外,我注意到如果您在根视图控制器上滑动(按下一个,然后再次返回),UI 会变得无响应(上面的答案也是同样的问题)。

所以子类 UINavigationController 中的代码应该如下所示:

@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
于 2013-11-20T14:59:11.200 回答
19

我用

[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"nav_back.png"]];

[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];
于 2013-11-05T09:37:27.093 回答
8

这是尼克 H247 答案的swift3 版本

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 {}
于 2017-03-27T13:23:36.440 回答
6

我还隐藏了后退按钮,将其替换为自定义的 leftBarItem。
推送操作后删除 interactivePopGestureRecognizer 委托对我有用:

[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;
}
于 2013-10-02T09:18:47.357 回答
6
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

这是来自http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks,但它会导致几个错误:

  1. 从屏幕左边缘滑入时,将另一个 viewController 推入 navigationController;
  2. 或者,当topViewController从navigationController弹出时,从屏幕左边缘向内滑动;

例如,当 navigationController 的 rootViewController 显示时,从屏幕左边缘滑入,然后点击某物(QUICKLY)将另一个ViewController 推入 navigationController,然后

  • rootViewController 不响应任何触摸事件;
  • anotherViewController 不会显示;
  • 再次从屏幕边缘滑动,将显示另一个ViewController;
  • 点击自定义后退按钮弹出anotherViewController,崩溃!

所以你必须像这样实现UIGestureRecognizerDelegate方法self.navigationController.interactivePopGestureRecognizer.delegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
        return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
    }
    return YES;
}
于 2013-12-19T02:59:26.920 回答
4

尝试self.navigationController.interactivePopGestureRecognizer.enabled = YES;

于 2013-09-27T16:42:50.567 回答
1

这不是我写的,但是下面的博客帮了我很多,并解决了我的自定义导航按钮问题:

http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/

总之,他实现了一个使用弹出手势委托的自定义 UINavigationController。非常干净和便携!

代码:

@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;
}

编辑。添加了对用户尝试在根视图控制器上向左滑动时出现的问题的修复:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] &&
        self.topViewController == [self.viewControllers firstObject] &&
        gestureRecognizer == self.interactivePopGestureRecognizer) {

        return NO;
    }

    return YES;
}
于 2014-11-11T20:06:57.787 回答
1

根视图

override func viewDidAppear(_ animated: Bool) {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}

子视图

override func viewDidLoad() {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
    self.navigationController?.interactivePopGestureRecognizer?.delegate = self
} 

extension ChildViewController: UIGestureRecognizerDelegate {}
于 2018-01-08T04:50:52.910 回答
1

使用此逻辑来保持启用或禁用滑动手势。

- (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;
        }
    }
}
于 2018-11-29T07:49:12.623 回答
0

我有一个类似的问题,我将当前视图控制器分配为交互式弹出手势的委托,但会破坏任何推送的视图或导航堆栈中视图下方的视图的手势。我解决这个问题的方法是将委托设置为 in -viewDidAppear,然后将其设置为 nil in -viewWillDisappear。这使我的其他视图能够正常工作。

于 2014-03-06T22:33:12.860 回答
0

想象一下,我们正在使用 Apple 的默认主/详细项目模板,其中 master 是一个表格视图控制器,点击它将显示详细视图控制器。

我们想要自定义出现在详细视图控制器中的后退按钮。这是如何自定义后退按钮的图像图像颜色文本、文本颜色和字体


要全局更改图像、图像颜色、文本颜色或字体,请将以下内容放在创建任何视图控制器之前调用的位置(例如application:didFinishLaunchingWithOptions:,这是一个好地方)。

- (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;
}

注意,您可以appearanceWhenContainedIn:更好地控制哪些视图控制器受这些更改的影响,但请记住,您不能通过[DetailViewController class],因为它包含在 UINavigationController 中,而不是您的 DetailViewController 中。这意味着如果您想更好地控制受影响的内容,则需要将 UINavigationController 子类化。

要自定义特定后退按钮项的文本或字体/颜色,您必须在MasterViewController(而不是 DetailViewController!)中进行。这似乎不直观,因为该按钮出现在 DetailViewController 上。但是,一旦您了解自定义它的方法是通过在 navigationItem 上设置属性,它就会开始变得更有意义。

- (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;
}

注意:在设置 self.navigationItem.backBarButtonItem 之后尝试设置 titleTextAttributes 似乎不起作用,因此必须在将值分配给此属性之前设置它们。

于 2014-10-03T19:56:46.477 回答
0

创建一个类“TTNavigationViewController”,它是“UINavigationController”的子类,并在故事板/类中创建该类的现有导航控制器,类中的示例代码 -

    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
}}
于 2016-11-30T10:55:08.933 回答