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

后端 未结 15 1532
被撕碎了的回忆
被撕碎了的回忆 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 13:01

    Note: this no longer works as of iOS 11

    In lieu of that mess with trying to find the UIBarButtonItem's view in the toolbar's subview list, you can also try this, once the item is added to the toolbar:

    [barButtonItem valueForKey:@"view"];
    

    This uses the Key-Value Coding framework to access the UIBarButtonItem's private _view variable, where it keeps the view it created.

    Granted, I don't know where this falls in terms of Apple's private API thing (this is public method used to access a private variable of a public class - not like accessing private frameworks to make fancy Apple-only effects or anything), but it does work, and rather painlessly.

    0 讨论(0)
  • 2020-12-07 13:02

    I tried @voi1d's solution, which worked great until I changed the title of the button that I had added a long press gesture to. Changing the title appears to create a new UIView for the button that replaces the original, thus causing the added gesture to stop working as soon as a change is made to the button (which happens frequently in my app).

    My solution was to subclass UIToolbar and override the addSubview: method. I also created a property that holds the pointer to the target of my gesture. Here's the exact code:

    - (void)addSubview:(UIView *)view {
        // This method is overridden in order to add a long-press gesture recognizer
        // to a UIBarButtonItem. Apple makes this way too difficult, but I am clever!
        [super addSubview:view];
        // NOTE - this depends the button of interest being 150 pixels across (I know...)
        if (view.frame.size.width == 150) {
            UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:targetOfGestureRecognizers 
                                                                                                    action:@selector(showChapterMenu:)];
            [view addGestureRecognizer:longPress];
        }
    }
    

    In my particular situation, the button I'm interested in is 150 pixels across (and it's the only button that is), so that's the test I use. It's probably not the safest test, but it works for me. Obviously you'd have to come up with your own test and supply your own gesture and selector.

    The benefit of doing it this way is that any time my UIBarButtonItem changes (and thus creates a new view), my custom gesture gets attached, so it always works!

    0 讨论(0)
  • 2020-12-07 13:08

    This is the most Swift-friendly and least hacky way I came up with. Works in iOS 12.

    Swift 5

    var longPressTimer: Timer?
    
    let button = UIButton()
    button.addTarget(self, action: #selector(touchDown), for: .touchDown)
    button.addTarget(self, action: #selector(touchUp), for: .touchUpInside)
    button.addTarget(self, action: #selector(touchCancel), for: .touchCancel)
    let undoBarButton = UIBarButtonItem(customView: button)
    
    @objc func touchDown() {
        longPressTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(longPressed), userInfo: nil, repeats: false)
    }
    
    @objc func touchUp() {
        if longPressTimer?.isValid == false { return } // Long press already activated
        longPressTimer?.invalidate()
        longPressTimer = nil
        // Do tap action
    }
    
    @objc func touchCancel() {
        longPressTimer?.invalidate()
        longPressTimer = nil
    }
    
    @objc func longPressed() {
        // Do long press action
    }
    
    0 讨论(0)
提交回复
热议问题