iOS 7中的“后退”按钮禁用滑动以向后导航


80

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


我尚未为此找到合适的解决方案?是否有任何人找到了一个好的解决方案,并解释了为什么可行??
user1010819 2013年

如何使用制作精良的第三方库:SwipeBack
devxoul

Answers:


82

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

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

self.navigationController.interactivePopGestureRecognizer.delegate = self;

编辑: 如果在init方法中调用,则此方法不起作用。应该以viewDidLoad类似方法调用它。


您是否在视图控制器上进行了设置?还是导航控制器?我有同样的问题,但这似乎不起作用?
凯文·伦斯克斯2013年

1
@mixedCase下载示例项目后,我现在了解您的问题。只要集合视图的内容不超过视图控制器的水平宽度,该代码就起作用。但是,一旦收藏夹视图可水平滚动,它就会覆盖InteractivePopGestureRecognizer。我将查看是否可以找到解决方法。
保罗·亨特

8
我对此代码有疑问。在5-10次回扫VC冻结后,它的工作时间不长。有什么办法吗?
Timur Bernikovich 2013年

1
Timur Bernikowich我也遇到了同样的情况。您是否发现任何原因?
user1010819

4
将委托设置为self从class对象取消设置_UINavigationInteractiveTransition。该对象的责任是确保导航控制器在处于过渡状态时不被告知弹出。仍在调查自定义后退按钮时是否有可能启用此手势。
Saltymule 2013年

56

尽可能使用UINavigationBar的backIndicatorImage和backIndicatorTransitionMaskImage属性。将这些设置在UIAppearanceProxy上可以轻松修改整个应用程序的行为。唯一的麻烦是,您只能在ios 7上进行设置,但这可以解决,因为无论如何您只能在ios 7上使用弹出手势。您的常规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的委托将导致很多问题。


4
谢谢Dan,这真的很重要,应该被接受。通过简单地重新分配代表,您就可以应对许多奇怪的行为。尤其是当用户尝试向后滑动时topViewController
克里斯·瓦格纳

1
这是一个解决方案,假设您要做的就是更改后退按钮图像。但是,如果您想更改后退按钮文本和/或单击后退按钮时执行的操作怎么办?
user102008 2014年

到那时,通过设置UINavigationItem.leftBarButtonItem会更好。通过在Google或stackoverflow上搜索leftBarButtonItem可以找到各种答案。
Saltymule 2014年

如果要将外观更改限制为自己的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"]];
tom

2
不幸的是,这在iOS8上不起作用。后退按钮不会将其外观更改为我的自定义图像。
Lensflare 2014年

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

1
在根视图控制器上刷卡时遇到了同样的问题,谢谢!
迈克尔·罗斯

整个页面中的最佳解决方案。效果很好。谢谢
Shahid Iqbal

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

2
我在其他地方看到过评论,由于苹果代码中的当前错误,这会在64位模式下引起问题-只是以为我会发布警告
彼得·约翰逊

@PeterJohnson什么样的错误?
art-divin 2014年

简直是最好的解决方案!
Igotit 2014年

有史以来最简单的解决方案。
tounaobun

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

4
这种方法的一个问题是,如果您在根视图上执行边缘平移,则会锁定UI。我遇到此问题是因为我将同一个视图的多个实例推送到导航堆栈。
Nikola Lajic 2013年

@MrNickBarker感谢您让我知道!您能描述一下具体情况吗?滑动根视图控制器时,我无法重现它
2013年

4
我是在viewDidLoad方法中设置的。我的最终解决方案是将另一个类设置为委托,如果导航堆栈中有多个视图控制器,则该委托仅对'gestureRecognizerShouldBegin'返回true。
Nikola Lajic

MrNickBarker冻结的任何原因我都遇到了同样的情况
user1010819 2013年

6
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

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

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

例如,当显示navigationController的rootViewController时,从屏幕的左边缘向内轻扫,然后轻按(快速)将另一个ViewController推入navigationController,然后

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

因此,您必须像这样实现UIGestureRecognizerDelegate方法self.navigationController.interactivePopGestureRecognizer.delegate

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

1
SwipeBack是这些问题的解决方案。
devxoul

6

这是Nick 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 {}

3

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


不,它已经启用了..问题似乎是它的委托不允许手势识别器启动,我在这里添加了我的解决方案作为另一个答案。
2013年

avishic,能否请您解释一下为什么设置代表会起作用?。
user1010819

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

如果在导航控制器的根视图控制器上向左滑动一次,则UI冻结。
JohnVanDijk

@JohnVanDijk我用我认为可以解决该问题的修复程序编辑了答案。已经有一段时间了,但是很有意义。基本上,如果顶视图控制器是根视图控制器,那么我们将不会响应“ interactivePopGestureRecognizer”
kgaidis 2015年

它躲在我的后退按钮
穆罕默德·侯赛因

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 {}

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

0

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


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似乎不起作用,因此必须在将值分配给此属性之前进行设置。


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
}}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.