mootymoots
mootymoots

Reputation: 4575

UINavigationBar Touch

I would like to fire an event on touch when a user taps the title of the navigation bar of one of my views.

I'm at a bit of a loss on whether I can access the view of the title of the UINavigationBar in order to wire up a touch event to it.

Is this even possible?

Upvotes: 21

Views: 22386

Answers (9)

Gavin
Gavin

Reputation: 1767

Building on this answer and this one, I went with the below "functional" approach, I am not sure it is anymore readable, but I prefer it over breaking out of a loop.

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

Upvotes: 0

Beau Nouvelle
Beau Nouvelle

Reputation: 7252

This is the simplest and easiest solution in Swift, just copy/paste and fill in the blanks:

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

Be sure to set the userInteractionEnabled to true on the titleView, its off by default.

Upvotes: 4

bizz84
bizz84

Reputation: 2272

This can be done by attaching a UITapGestureRecognizer to the navigation bar subview that corresponds to the titleView.

As the titleView's index varies depending on how many bar button items there are, it is necessary to iterate through all the navigation bar subviews to find the correct view.

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")
  }
}

Upvotes: 1

user262490
user262490

Reputation:

The solution I found is a button, I use the following (but I don't know how "legal" it is):

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;

Put that bit of code where ever you set your title. Then elsewhere I had this to test:

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

which logged "Title tap" on the console!

The way I've gone about this may be completely wrong, but might give you an idea on what you can look in to. It's certainly helped me out! There's probably a better way of doing it though.

Upvotes: 30

Rik Smith-Unna
Rik Smith-Unna

Reputation: 3475

None of the other answers worked well for me. Rather than add a gesture to an existing subview of the navigationBar, or replace the titleView, I simply added a clear UIView covering a good portion of the 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];
}

Upvotes: 26

JapCon
JapCon

Reputation: 448

One of the possible options: (Use 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;

Upvotes: 1

Ortwin Gentz
Ortwin Gentz

Reputation: 54121

I didn't want to specify a button as a custom titleView because that would mean I can't use the standard title anymore. On the other hand, when adding a tap gesture recognizer to the navigation bar, we have to make sure that it doesn't fire when tapping a bar button.

This solution accomplishes both (to be added to a UINavigationBar subclass):

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

It automatically sends a -titleViewTapped: message to the view controller (if implemented). In a UITableViewController subclass you could implement the method like this for a scroll to top feature:

- (void)titleViewTapped:(id)sender {
    [self.tableView setContentOffset:CGPointMake(0, -self.tableView.contentInset.top) animated:YES];
}

Caution: we're retrieving the title view using the undocumented -valueForKey:@"titleView". It's technically not using a private API but might still fail in a future iOS version!

Upvotes: 1

phmagic
phmagic

Reputation: 691

You can add a gesture recognizer to be a single tap to the title of the navigation controller. I found that in the navigationBar subviews, the title is the one at index 1, the left button's index is 0 and the right button's index is 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];

and then implement the following somewhere in your implementation.

-(void)navSingleTap

So you can use this for a single tap, or you can implement any gesture recognizer you want on that title.

Upvotes: 43

Alex Reynolds
Alex Reynolds

Reputation: 96937

The UINavigationItem class reference has a titleView property, which you can set to your custom UIView.

In other words, make a subclass of UIView with your touch handlers, and then when you push your navigation item, set that item's titleView property to an instance of your subclass.

Upvotes: 9

Related Questions