在 iOS 7 中更改后退按钮会禁用滑动导航

Posted

技术标签:

【中文标题】在 iOS 7 中更改后退按钮会禁用滑动导航【英文标题】:Changing back button in iOS 7 disables swipe to navigate back 【发布时间】:2013-10-03 23:29:55 【问题描述】:

我有一个 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,但这似乎没有显示我的自定义图像。

【问题讨论】:

我还没有找到合适的解决方案??有没有人找到了一个好的解决方案并解释为什么它有效?? 如何使用制作精良的第三方库:SwipeBack? 【参考方案1】:

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

【讨论】:

不,它已经启用了。问题似乎是它的代表不允许手势识别器启动,我在这里添加了我的解决方案作为另一个答案。 avishic 你能解释一下为什么设置代表会起作用吗?...我遇到了同样的问题。【参考方案2】:

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

在分配leftBarButtonItem 后调用以下行对我有用:

self.navigationController.interactivePopGestureRecognizer.delegate = self;

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

【讨论】:

你在视图控制器上设置了吗?还是导航控制器?我有同样的问题,但这似乎不起作用? @mixedCase 下载您的示例项目后,我现在了解您的问题。只要集合视图的内容不超过视图控制器的水平宽度,代码就可以工作。但是,一旦集合视图变为可水平滚动,它就会覆盖 interactivePopGestureRecognizer。我会看看我是否能找到解决方法。 我对这段代码有疑问。在 5-10 次后滑 VC 冻结后,它的工作时间不长。有什么解决办法吗? Timur Bernikowich 我也遇到过这种情况。您找到原因了吗? 将委托设置为 self 会将其从 _UINavigationInteractiveTransition 类的对象中取消设置。该对象的责任是确保导航控制器在已经处于转换状态时不会被告知弹出。仍在研究是否可以在自定义后退按钮时启用此手势。【参考方案3】:

我还隐藏了后退按钮,将其替换为自定义 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;

【讨论】:

此方法的一个问题是,如果您在根视图上执行边缘平移,它将锁定 UI。我遇到这种情况是因为我将同一视图的多个实例推送到导航堆栈。 @MrNickBarker 感谢您告诉我!你能描述一下具体的场景吗?刷根视图控制器时无法重现 我在 viewDidLoad 方法中设置它。我的最终解决方案是将另一个类设置为委托,如果导航堆栈中有多个视图控制器,则它只为“gestureRecognizerShouldBegin”返回 true。 MrNickBarker 任何冻结原因我都遇到了同样的情况【参考方案4】:

我用

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

[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];

【讨论】:

我在其他地方看到过 cmets,由于目前 Apple 代码中存在错误,这会导致 64 位模式出现问题 - 只是想我会发布警告 @PeterJohnson 什么样的bug? 简直就是最好的解决方案! 有史以来最简单的解决方案。【参考方案5】:

我看到了这个解决方案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

【讨论】:

在根视图控制器上滑动时遇到了同样的问题,谢谢! 整页中的最佳解决方案。效果很好。谢谢【参考方案6】:

尽可能使用 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 的委托会导致很多问题。

【讨论】:

谢谢丹,这真的很重要,应该是公认的答案。通过简单地重新分配代表,您可以向很多奇怪的行为敞开心扉。特别是当用户尝试在topViewController 上滑动时。 这是一个假设您要做的只是更改后退按钮图像的解决方案。但是,如果您想更改后退按钮文本和/或单击后退按钮时执行的操作怎么办? 此时通过设置 UINavigationItem.leftBarButtonItem 会更好地为您服务。在google或***上搜索leftBarButtonItem可以找到各种各样的答案。 如果您想将外观更改限制为您自己的 UI,并且不影响例如由 ABPeoplePickerNavigationController 创建的导航栏,您可以使用自定义 UINavigationController 子类:[[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorImage:[UIImage imageNamed:@"btn_back_arrow"]]; [[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"btn_back_arrow_highlighted"]]; 不幸的是,这在 iOS8 上不起作用。后退按钮不会将其外观更改为我的自定义图像。【参考方案7】:
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

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

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

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

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

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

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 
    if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) 
        return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
    
    return YES;

【讨论】:

SwipeBack 是这些问题的解决方案。【参考方案8】:

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

【讨论】:

【参考方案9】:

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

我们想要自定义出现在详细视图控制器中的后退按钮。这是如何自定义返回按钮的image、image color、text、文字颜色和font。


要全局更改图像、图像颜色、文本颜色或字体,请将以下内容放在一个名为在创建任何视图控制器之前的位置(例如,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 似乎不起作用,因此必须在将值分配给此属性之前设置它们。

【讨论】:

【参考方案10】:

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

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;

【讨论】:

如果在导航控制器的根视图控制器上向左滑动,UI 会冻结。 @JohnVanDijk 我编辑了答案,我认为我实施了一个修复程序来解决这个问题。已经有一段时间了,但它是有道理的。基本上,如果顶视图控制器是根视图控制器,那么我们将不会响应 'interactivePopGestureRecognizer' 它隐藏了我的后退按钮【参考方案11】:

创建一个类“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

【讨论】:

【参考方案12】:

这里是Nick H247's answer的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 

【讨论】:

【参考方案13】:

根视图

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 

【讨论】:

【参考方案14】:

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

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

【讨论】:

以上是关于在 iOS 7 中更改后退按钮会禁用滑动导航的主要内容,如果未能解决你的问题,请参考以下文章

滑动时禁用网页导航(后退和前进)

如何检测由滑动触发的后退导航并在iOS上将其停止?

Xamarin iOS 禁用导航滑动效果

如何在 iOS 7 上的 UINavigationController 中禁用向后滑动手势

禁用后退按钮导航操作

在反应导航中禁用后退按钮