导航栏触摸问题

21
我希望在用户点击我的视图导航栏标题时触发一个事件。关于是否可以访问UINavigationBar的标题视图以便将触摸事件与其连接,我有些困惑。这是可能的吗?
9个回答

43

您可以向导航控制器的标题添加手势识别器以进行单击操作。 我发现在navigationBar的子视图中,标题的索引为1,左侧按钮的索引为0,右侧按钮的索引为2。

UITapGestureRecognizer *navSingleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(navSingleTap)];
navSingleTap.numberOfTapsRequired = 1;
[[self.navigationController.navigationBar.subviews objectAtIndex:1] setUserInteractionEnabled:YES];
[[self.navigationController.navigationBar.subviews objectAtIndex:1] addGestureRecognizer:navSingleTap];

然后在你的实现中的某个地方实现以下内容。

-(void)navSingleTap

所以你可以将其用于单击,或者在该标题上实现任何你想要的手势识别器。


如果您不想设置自定义的titleView,这个方法就可以使用。(我没有设置自定义的titleView。) - zekel
有没有可能设置一种色调颜色,让它看起来更像一个按钮?我尝试过使用setTintColorsetTintAdjustmentMode,但都不起作用。 - Adam Carter
这绝对似乎是最有效的答案。谢谢~ - James
我发现我必须将这个放在viewDidLoad方法中,以便在深入导航栏后返回时使其正常工作,否则所有的导航栏项目都会在此之后激活它。 - James
嗨~ @phmagic,我发现它只适用于 topViewController,而不是 secondViewController。你知道为什么吗?有人遇到过同样的问题吗? - Jenny
尽管为此创建视图似乎很麻烦,但另一方面,使用这种方法代替是一种hack,可能会在未来的iOS版本中出现问题。如果您关心自己的代码,请不要这样做。 - Zsolt Szatmari

30
我找到的解决方案是一个按钮,我使用了以下代码(但我不知道它是否“合法”):
UIButton *titleLabelButton = [UIButton buttonWithType:UIButtonTypeCustom];
[titleLabelButton setTitle:@"myTitle" forState:UIControlStateNormal];
titleLabelButton.frame = CGRectMake(0, 0, 70, 44);
titleLabelButton.font = [UIFont boldSystemFontOfSize:16];
[titleLabelButton addTarget:self action:@selector(didTapTitleView:) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.titleView = titleLabelButton;

将那段代码放在设置标题的任何位置。然后我在其他地方进行了测试:

- (IBAction)didTapTitleView:(id) sender
{
    NSLog(@"Title tap");
}

在控制台上记录了“标题点击”!

我处理这个问题的方式可能完全错误,但可能会给你提供一些想法。它确实帮助了我!不过可能有更好的方法。


1
虽然它朝着正确的方向发展,但这很丑陋——UIButton的字体属性已被弃用,即使使用该属性,标题文本看起来也与正常情况非常不同。为什么要费心去做一个按钮呢?一个清晰的UIView会更好地工作。 - Rik Smith-Unna
使用UILabel没有问题。只需确保将label.userInteractionEnabled = YES设置为是。如果不这样做,标签将无法响应触摸事件。 - JapCon

26

其他答案对我都不太适用。我不是在现有的navigationBar子视图上添加手势,也不是替换titleView,而是简单地添加了一个清晰的UIView,覆盖了navigationBar的很大一部分...

- (void) setupNavbarGestureRecognizer {
    // recognise taps on navigation bar to hide
    UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showHideNavbar)];
    gestureRecognizer.numberOfTapsRequired = 1;
    // create a view which covers most of the tap bar to
    // manage the gestures - if we use the navigation bar
    // it interferes with the nav buttons
    CGRect frame = CGRectMake(self.view.frame.size.width/4, 0, self.view.frame.size.width/2, 44);
    UIView *navBarTapView = [[UIView alloc] initWithFrame:frame];
    [self.navigationController.navigationBar addSubview:navBarTapView];
    navBarTapView.backgroundColor = [UIColor clearColor];
    [navBarTapView setUserInteractionEnabled:YES];
    [navBarTapView addGestureRecognizer:gestureRecognizer];
}

这是目前为止唯一有效的方法,尽管我似乎需要添加:[self.navigationController.navigationBar setUserInteractionEnabled:YES]; 我仍然有按钮干扰问题,因为变量按钮名称是我的应用程序的一部分 - 希望有一种一致的方式只是将可触摸性附加到标题(在我的情况下是UIImageView)。 - Sam Joseph
1
啊哈,我终于让标题可点击了 - 方法如下:http://pastebin.com/eFwTShhz - Sam Joseph
@SamJoseph,你应该将那个解决方案发布为答案。 - Rik Smith-Unna
这个很好,因为它不会干扰像返回按钮之类的东西。 - pixelrevision

9
UINavigationItem类参考文档有一个titleView属性,您可以将其设置为自定义的UIView
换句话说,创建一个带有触摸处理程序的UIView子类,然后在推送导航项时,将该项的titleView属性设置为您子类的一个实例。

谢谢,但是我该如何对此触摸做出反应,例如 - 如果点击titleView,我怎样才能在我的整体视图控制器中调用一个方法? - mootymoots
此外,使用这种方法,我的标题属性丢失了。你能进一步解释一下吗?我添加了一个touchesBegan处理程序,其中包含一个简单的NSLog(@"touched"); 但是当点击titleView时它没有任何反应。我正在创建一个子类的实例,并将其分配给即将推送的视图实例,即view.navigationItem.titleView = mySubclass; - mootymoots
您的自定义 UIView 可以有许多属性,包括用于替换标题的 UILabel 和一个用于“父”视图控制器的 UIViewController 属性,当您创建自定义视图的实例时设置。还要检查您的视图是否具有足够大小的框架。 - Alex Reynolds

4

这是在Swift中最简单、最容易的解决方案,只需复制/粘贴并填写空白部分:

let navTapGesture = UITapGestureRecognizer(target: <#T##AnyObject?#>, action: <#T##Selector#>)
navigationItem.titleView.userInteractionEnabled = true
navigationItem.titleView.addGestureRecognizer(navTapGesture)

请确保在titleView上将userInteractionEnabled设置为true,默认情况下它是关闭的。


你好,如果我们不移除手势,这样可以吗? - Yogesh Patel
1
当然可以。没问题。 - Beau Nouvelle

1
这可以通过将UITapGestureRecognizer附加到对应于titleView的导航栏子视图来完成。
由于titleView的索引取决于有多少个栏按钮项,因此需要遍历所有导航栏子视图以找到正确的视图。
import UIKit

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    addTitleViewTapAction("tapped", numberOfTapsRequired: 3)
  }

  func addTitleViewTapAction(action: Selector, numberOfTapsRequired: Int) {

    if let subviews = self.navigationController?.navigationBar.subviews {
      for subview in subviews {
        // the label title is a subview of UINavigationItemView
        if let _ = subview.subviews.first as? UILabel {
          let gesture = UITapGestureRecognizer(target: self, action: action)
          gesture.numberOfTapsRequired = numberOfTapsRequired
          subview.userInteractionEnabled = true
          subview.addGestureRecognizer(gesture)
          break
        }
      }
    }
  }

  @objc func tapped() {

    print("secret tap")
  }
}

1
可能的选项之一:(使用UILabel)
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doSomething:)];
UILabel * titleView = [UILabel new];
titleView.text = @"Test";
[titleView sizeToFit];
titleView.userInteractionEnabled = YES;
[titleView addGestureRecognizer:tapGesture];

self.navigationItem.titleView = titleView;

1
我不想将按钮指定为自定义的titleView,因为这意味着我不能再使用标准标题。另一方面,在向导航栏添加轻拍手势识别器时,我们必须确保它在点击工具栏按钮时不会触发。

这个解决方案可以同时解决这两个问题(需要添加到UINavigationBar子类中):

- (void)awakeFromNib {
    // put in -initWithFrame: if initialized manually
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(ft_titleTapped:)];
    [self addGestureRecognizer:tap];
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    UIView *titleView = [self valueForKey:@"titleView"];
    CGRect titleFrame = titleView.frame;
    titleFrame.origin.y = 0; // expand to full height of navBar
    titleFrame.size.height = self.frame.size.height;
    return CGRectContainsPoint(titleFrame, [gestureRecognizer locationInView:self]);
}

- (void)ft_titleTapped:(UITapGestureRecognizer*)sender {
    if (sender.state == UIGestureRecognizerStateEnded) {
        // could add some checks here that the delegate is indeed a navigation controller
        UIViewController<FTViewControllerAdditions> *viewController = (id)[((UINavigationController*)self.delegate) topViewController];
        if ([viewController respondsToSelector:@selector(titleViewTapped:)]) {
            [viewController titleViewTapped:self];
        }
    }
}

它会自动向视图控制器发送一个-titleViewTapped:消息(如果已实现)。在UITableViewController子类中,您可以像这样实现该方法以实现滚动到顶部功能:
- (void)titleViewTapped:(id)sender {
    [self.tableView setContentOffset:CGPointMake(0, -self.tableView.contentInset.top) animated:YES];
}

注意:我们正在使用未公开的-valueForKey:@"titleView"方法来检索标题视图。这在技术上并不是使用私有API,但在将来的iOS版本中可能仍然会失败!


这个答案对我有用,通过在UINavigationBar上的类别中覆盖-[gestureRecognizerShouldBegin]并将该类别导入到我的视图控制器中。 - Ken M. Haggerty

0

这个答案这一个的基础上,我采用了下面的“函数式”方法,我不确定它是否更易读,但我更喜欢它而不是跳出循环。

    if let navTitle = self.navigationController?.navigationBar.subviews.first(where: {$0.subviews.first is UILabel}) {
        let gesture = UITapGestureRecognizer(target: self, action: action)
        gesture.numberOfTapsRequired = numberOfTapsRequired
        navTitle.isUserInteractionEnabled = true
        navTitle.addGestureRecognizer(gesture)
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接