14

I want to add functionality to my back buttons through my UINavigationController-based app where long-pressing the back button will pop to root. However, I can't figure out where to attach the gesture recognizer. Do I subclass UINavigationBar and try and detect if the long press is in the left button region?

I've heard of people adding similar functionality before. Anyone have any ideas?

kevboh
  • 5,207
  • 5
  • 38
  • 54

4 Answers4

18

I know this question is old, but I came up with a solution. Instead of trying to add the gesture recognizer to the button itself (which would be ideal), I added it to the self.navigationController.navigationBar and then in the action method, use the locationInView to see if I'm over the back button. I wasn't entirely sure about how to identify the back button precisely, so I'm clumsily just grabbing the the first subview with an x coordinate less than some arbitrary value, but it seems promising. If someone has a better way to identify the frame of the back button, let me know.

- (void)longPress:(UILongPressGestureRecognizer *)sender 
{
    if (sender.state == UIGestureRecognizerStateEnded)
    {
        // set a default rectangle in case we don't find the back button for some reason

        CGRect rect = CGRectMake(0, 0, 100, 40);

        // iterate through the subviews looking for something that looks like it might be the right location to be the back button

        for (UIView *subview in self.navigationController.navigationBar.subviews)
        {
            if (subview.frame.origin.x < 30) 
            {
                rect = subview.frame;
                break;
            }
        }

        // ok, let's get the point of the long press

        CGPoint longPressPoint = [sender locationInView:self.navigationController.navigationBar];

        // if the long press point in the rectangle then do whatever

        if (CGRectContainsPoint(rect, longPressPoint))
            [self doWhatever];
    }
}

- (void)addLongPressGesture
{
    if (NSClassFromString(@"UILongPressGestureRecognizer"))
    {
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
        [self.navigationController.navigationBar addGestureRecognizer:longPress];
        [longPress release];
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Cool idea. Next time I need to do this I'll try your approach out. Thanks for the follow-up! – kevboh Apr 04 '12 at 12:18
  • I'm fine with just any long press there to pop - seems reasonable to me. – David H Oct 24 '13 at 00:34
  • I had the same requirement like setting a longPress Gesture on back button.The problem with this approach is that the back button doesn't remain in highlighted state.So I set the cancelsTouchesInView propery to NO.BUt then both my longPressHandler and backButton handler gets called.Any solutions for this?? – user1010819 Nov 26 '13 at 12:50
  • @user1010819 No, nothing leaps out at me. To be honest, though, if I were to tackle this today, I undoubtedly would not use this hack to add long-press gesture to standard navigation controller (because it's simply confusing for the end user to use custom behavior on a standard UI control like the standard navigation bar). I'd replace the back button with my own graphical button that looks different (to let user know things work differently in this app), possibly replacing the whole navigation controller and bar with my own custom container class. – Rob Nov 26 '13 at 13:02
  • Hmm. I did the same thing.But using custom back button messes up with interactive pop gesture.And after wasting 2-3 days I couldn't proper working solution so I am going ahead with this and I found a solution to my problem with approach.To prevent backBarButtonItem action handler to perform I set cancelsTouchesInView to YES in UILongPressGestureRecognizerStateEnded.This works. – user1010819 Nov 26 '13 at 16:45
6

I believe UIGestureRecognizers can only be added to UIViews and subclasses of UIViews.

http://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html

The back button is a UIBarButtonItem that descends from NSObject. Therefore, you won't be able to attach a gesture recognizer to a standard back button using

UILongPressGestureRecognizer *longPressGesture =
            [[[UILongPressGestureRecognizer alloc]
              initWithTarget:self action:@selector(longPress:)] autorelease];

[self.navigationItem.backBarButtonItem addGestureRecognizer:longPressGesture];

You can however add a custom view to a UIBarButtonItem. A custom view could just as easily be a UIView, UIButton, UILabel, etc.

Example:

UIView *myTransparentGestureView = [[UIView alloc] initWithFrame:CGRectMake(0,0,40,30)];
[myTransparentGestureView addGestureRecognizer:longPressGesture];
[self.navigationItem.backBarButtonItem setCustomView:myTransparentGestureView];
// Or you could set it like this
// self.navigationItem.backBarButtonItem.customView = myTransparentGestureView;
[myTransparentGestureView release];

You have to be careful however, since setting properties on backBarButtonItem applies to the next view that you push. So if you have view A that pushes to view B and you want the gesture to be recognized when you tap back in view B. You must set it up in view A.

Moshe
  • 57,511
  • 78
  • 272
  • 425
Andrew
  • 2,690
  • 23
  • 27
  • Adding a gesture recognizer to a custom view on the backButtomItem didn't work for me... the recognizer refuses to fire. Were you able to get it working with the code above? – kevboh Jun 29 '11 at 21:14
  • It's likely not working because the backBarButtonItem is read only, so it isn't accepting a custom view. You'd most likely need to create your own leftbarbuttonitem like this answer. http://stackoverflow.com/questions/526520/how-to-create-backbarbuttomitem-with-custom-view-for-a-uinavigationcontroller – Andrew Jun 29 '11 at 21:30
  • Ah, but then I lose my back arrow unless I find an image... probably not worth it. Thanks anyway, though! – kevboh Jun 30 '11 at 14:18
4

I followed a slightly different path, figured I'd share it. The above answers are fine, but really, if the long press is in the leading 1/3 of the nav bar, that's good enough for me:

- (void)longPress:(UILongPressGestureRecognizer *)gr
{
    NSLog(@"longPress:");
    UINavigationBar *navBar = [self navigationBar];
    CGFloat height = navBar.bounds.size.height;
    CGPoint pt = [gr locationOfTouch:0 inView:navBar];
    //NSLog(@"PT=%@ height=%f", NSStringFromCGPoint(pt), height);
    if(CGRectContainsPoint(CGRectMake(0,0,100,height), pt)) {
        [self popToViewController:self.viewControllers[0] animated:YES];
    }
}
David H
  • 40,852
  • 12
  • 92
  • 138
1

Here's my solution:

In appDelegate (the "owner" of the nav bar in my app), In applicationDidFinishLaunchingWithOptions:

Get the nav bar view and add the gesture recognizer to the whole view:

// Get the nav bar view
UINavigationBar *myNavBar = nil;
for (UIView *view in [self.window.rootViewController.view subviews]) {
    if ([view isKindOfClass:[UINavigationBar class]]) {
        NSLog(@"Found Nav Bar!!!");
        myNavBar = (UINavigationBar *)view;
    }
}

UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                                                                        action:@selector(backButtonLongPress:)];
[myNavBar addGestureRecognizer:longPress];
NSLog(@"Gesture Recognizer Added.");

Then in appDelegate, in -(void) backButtonLongPress:(id) sender

Check to see if the gesture occurs within the frame of the back button:

if ([sender state] == UIGestureRecognizerStateBegan) {

    // Get the nav bar view
    UINavigationBar *myNavBar = nil;
    for (UIView *view in [self.window.rootViewController.view subviews]) {
        if ([view isKindOfClass:[UINavigationBar class]]) {
            NSLog(@"Found Nav Bar!!!");
            myNavBar = (UINavigationBar *)view;
        }
    }

    // Get the back button view
    UIView *backButtonView = nil;
    for (UIView *view in [myNavBar subviews]) {
        if ([[[view class] description] isEqualToString:@"UINavigationItemButtonView"]) {
            backButtonView = view;
            NSLog(@"Found It: %@", backButtonView);
            NSLog(@"Back Button View Frame: %f, %f; %f, %f", backButtonView.frame.origin.x, backButtonView.frame.origin.y, backButtonView.frame.size.width, backButtonView.frame.size.height);
        }
    }

    CGPoint longPressPoint = [sender locationInView:myNavBar];
    NSLog(@"Touch is in back button: %@", CGRectContainsPoint(backButtonView.frame, longPressPoint) ? @"YES" : @"NO");
    if (CGRectContainsPoint(backButtonView.frame, longPressPoint)) {
        // Place your action here
    }

    // Do nothing if outside the back button frame

}
Peter
  • 201
  • 2
  • 4