How can you add a UIGestureRecognizer to a UIBarButtonItem as in the common undo/redo UIPopoverController scheme on iPad apps?

后端 未结 15 1575
被撕碎了的回忆
被撕碎了的回忆 2020-12-07 12:36

Problem

In my iPad app, I cannot attach a popover to a button bar item only after press-and-hold events. But this seems to be standard for

相关标签:
15条回答
  • 2020-12-07 12:53

    Instead of groping around for a subview you can create the button on your own and add a button bar item with a custom view. Then you hook up the GR to your custom button.

    0 讨论(0)
  • 2020-12-07 12:53

    @voi1d's 2nd option answer is the most useful for those not wanting to rewrite all the functionality of UIBarButtonItem's. I wrapped this in a category so that you can just do:

    [myToolbar addGestureRecognizer:(UIGestureRecognizer *)recognizer toBarButton:(UIBarButtonItem *)barButton];
    

    with a little error handling in case you are interested. NOTE: each time you add or remove items from the toolbar using setItems, you will have to re-add any gesture recognizers -- I guess UIToolbar recreates the holding UIViews every time you adjust the items array.

    UIToolbar+Gesture.h

    #import <UIKit/UIKit.h>
    
    @interface UIToolbar (Gesture)
    
    - (void)addGestureRecognizer:(UIGestureRecognizer *)recognizer toBarButton:(UIBarButtonItem *)barButton;
    
    @end
    

    UIToolbar+Gesture.m

    #import "UIToolbar+Gesture.h"
    
    @implementation UIToolbar (Gesture)
    
    - (void)addGestureRecognizer:(UIGestureRecognizer *)recognizer toBarButton:(UIBarButtonItem *)barButton {
      NSUInteger index = 0;
      NSInteger savedTag = barButton.tag;
    
      barButton.tag = NSNotFound;
      for (UIBarButtonItem *anItem in [self items]) {
        if (anItem.enabled) {
          anItem.tag = index;
          index ++;
        }
      }
      if (NSNotFound != barButton.tag) {
        [[[self subviews] objectAtIndex:barButton.tag] addGestureRecognizer:recognizer];
      }
      barButton.tag = savedTag;
    }
    
    @end
    
    0 讨论(0)
  • 2020-12-07 12:53

    @utopians answer in Swift 4.2

    @objc func myAction(_ sender: UIBarButtonItem, forEvent event:UIEvent) {
        let longPressed:Bool = (event.allTouches?.first?.tapCount).map {$0 == 0} ?? false
    
        ... handle long press ...
    }
    
    0 讨论(0)
  • 2020-12-07 12:56

    I know this is old but I spent a night banging my head against the wall trying to find an acceptable solution. I didn't want to use the customView property because would get rid of all of the built in functionality like button tint, disabled tint, and the long press would be subjected to such a small hit box while UIBarButtonItems spread their hit box out quite a ways. I came up with this solution that I think works really well and is only a slight pain to implement.

    In my case, the first 2 buttons on my bar would go to the same place if long pressed, so I just needed to detect that a press happened before a certain X point. I added the long press gesture recognizer to the UIToolbar (also works if you add it to a UINavigationBar) and then added an extra UIBarButtonItem that's 1 pixel wide right after the 2nd button. When the view loads, I add a UIView that's a single pixel wide to that UIBarButtonItem as it's customView. Now, I can test the point where the long press happened and then see if it's X is less than the X of the customview's frame. Here's a little Swift 3 Code

    @IBOutlet var thinSpacer: UIBarButtonItem!
    
    func viewDidLoad() {
        ...
        let thinView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 22))
        self.thinSpacer.customView = thinView
    
        let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(gestureRecognizer:)))
        self.navigationController?.toolbar.addGestureRecognizer(longPress)
        ...
    }
    
    func longPressed(gestureRecognizer: UIGestureRecognizer) {
        guard gestureRecognizer.state == .began, let spacer = self.thinSpacer.customView else { return }
        let point = gestureRecognizer.location(ofTouch: 0, in: gestureRecognizer.view)
        if point.x < spacer.frame.origin.x {
            print("Long Press Success!")
        } else {
            print("Long Pressed Somewhere Else")
        }
    }
    

    Definitely not ideal, but easy enough for my use case. If you need a specify a long press on specific buttons in specific locations, it gets a little more annoying but you should be able to surround the buttons you need to detect the long press on with thin spacers and then just check that your point's X is between both of those spacers.

    0 讨论(0)
  • 2020-12-07 12:57

    While this question is now over a year old, this is still a pretty annoying problem. I've submitted a bug report to Apple (rdar://9982911) and I suggest that anybody else who feels the same duplicate it.

    0 讨论(0)
  • 2020-12-07 12:57

    I tried something similar to what Ben suggested. I created a custom view with a UIButton and used that as the customView for the UIBarButtonItem. There were a couple of things I didn't like about this approach:

    • The button needed to be styled to not stick out like a sore thumb on the UIToolBar
    • With a UILongPressGestureRecognizer I didn't seem to get the click event for "Touch up Inside" (This could/is most likely be programing error on my part.)

    Instead I settled for something hackish at best but it works for me. I'm used XCode 4.2 and I'm using ARC in the code below. I created a new UIViewController subclass called CustomBarButtonItemView. In the CustomBarButtonItemView.xib file I created a UIToolBar and added a single UIBarButtonItem to the toolbar. I then shrunk the toolbar to almost the width of the button. I then connected the File's Owner view property to the UIToolBar.

    Interface Builder view of CustomBarButtonViewController

    Then in my ViewController's viewDidLoad: message I created two UIGestureRecognizers. The first was a UILongPressGestureRecognizer for the click-and-hold and second was UITapGestureRecognizer. I can't seem to properly get the action for the UIBarButtonItem in the view so I fake it with the UITapGestureRecognizer. The UIBarButtonItem does show itself as being clicked and the UITapGestureRecognizer takes care of the action just as if the action and target for the UIBarButtonItem was set.

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib
    
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestured)];
    
        UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(buttonPressed:)];
    
        CustomBarButtomItemView* customBarButtonViewController = [[CustomBarButtomItemView alloc] initWithNibName:@"CustomBarButtonItemView" bundle:nil];
    
        self.barButtonItem.customView = customBarButtonViewController.view;
    
        longPress.minimumPressDuration = 1.0;
    
        [self.barButtonItem.customView addGestureRecognizer:longPress];
        [self.barButtonItem.customView addGestureRecognizer:singleTap];        
    
    }
    
    -(IBAction)buttonPressed:(id)sender{
        NSLog(@"Button Pressed");
    };
    -(void)longPressGestured{
        NSLog(@"Long Press Gestured");
    }
    

    Now when a single click occurs in the ViewController's barButtonItem (Connected via the xib file) the tap gesture calls the buttonPressed: message. If the button is held down longPressGestured is fired.

    For changing the appearance of the UIBarButton I'd suggest making a property for CustomBarButtonItemView to allow access to the Custom BarButton and store it in the ViewController class. When the longPressGestured message is sent you can change the system icon of the button.

    One gotcha I've found is the customview property takes the view as is. If you alter the custom UIBarButtonitem from the CustomBarButtonItemView.xib to change the label to @"really long string" for example the button will resize itself but only the left most part of the button shown is in the view being watched by the UIGestuerRecognizer instances.

    0 讨论(0)
提交回复
热议问题