views:

590

answers:

2

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 undo/redo. How do other apps do this?

Background

I have an undo button (UIBarButtonSystemItemUndo) in the toolbar of my UIKit (iPad) app. When I press the undo button, it fires it's action which is undo:, and that executes correctly.

However, the "standard UE convention" for undo/redo on iPad is that pressing undo executes an undo but pressing and holding the button reveals a popover controller where the user selected either "undo" or "redo" until the controller is dismissed.

The normal way to attach a popover controller is with presentPopoverFromBarButtonItem:, and I can configure this easily enough. To get this to show only after press-and-hold we have to set a view to respond to "long press" gesture events as in this snippet:

UILongPressGestureRecognizer *longPressOnUndoGesture = [[UILongPressGestureRecognizer alloc] 
       initWithTarget:self 
               action:@selector(handleLongPressOnUndoGesture:)];
//Broken because there is no customView in a UIBarButtonSystemItemUndo item
[self.undoButtonItem.customView addGestureRecognizer:longPressOnUndoGesture];
[longPressOnUndoGesture release];

With this, after a press-and-hold on the view the method handleLongPressOnUndoGesture: will get called, and within this method I will configure and display the popover for undo/redo. So far, so good.

The problem with this is that there is no view to attach to. self.undoButtonItem is a UIButtonBarItem, not a view.

Possible solutions

1) [The ideal] Attach the gesture recognizer to the button bar item. It is possible to attach a gesture recognizer to a view, but UIButtonBarItem is not a view. It does have a property for .customView, but that property is nil when the buttonbaritem is a standard system type (in this case it is).

2) Use another view. I could use the UIToolbar but that would require some weird hit-testing and be an all around hack, if even possible in the first place. There is no other alternative view to use that I can think of.

3) Use the customView property. Standard types like UIBarButtonSystemItemUndo have no customView (it is nil). Setting the customView will erase the standard contents which it needs to have. This would amount to re-implementing all the look and function of UIBarButtonSystemItemUndo, again if even possible to do.

Question

How can I attach a gesture recognizer to this "button"? More specifically, how can I implement the standard press-and-hold-to-show-redo-popover in an iPad app?

Ideas? Thank you very much, especially if someone actually has this working in their app (I'm thinking of you, omni) and wants to share...

+1  A: 

Option 1 is indeed possible. Unfortunately it's a painful thing to find the UIView that the UIBarButtonItem creates. Here's how I found it:

[[[myToolbar subviews] objectAtIndex:[[myToolbar items] indexOfObject:myBarButton]] addGestureRecognizer:myGesture];

This is more difficult than it ought to be, but this is clearly designed to stop people from fooling around with the buttons look and feel.

Note that Fixed/Flexible spaces are not counted as views!

In order to handle spaces you must have some way of detecting them, and sadly the SDK simply has no easy way to do this. There are solutions and here are a few of them:

1) Set the UIBarButtonItem's tag value to it's index from left to right on the toolbar. This requires too much manual work to keep it in sync IMO.

2) Set any spaces' enabled property to NO. Then use this code snippet to set the tag values for you:

NSUInteger index = 0;
for (UIBarButtonItem *anItem in [myToolbar items]) {
    if (anItem.enabled) {
        // For enabled items set a tag.
        anItem.tag = index;
        index ++;
    }
}

// Tag is now equal to subview index.
[[[myToolbar subviews] objectAtIndex:myButton.tag] addGestureRecognizer:myGesture];

Of course this has a potential pitfall if you disable a button for some other reason.

3) Manually code the toolbar and handle the indexes yourself. As you'll be building the UIBarButtonItem's yourself, so you'll know in advance what index they'll be in the subviews. You could extend this idea to collecting up the UIView's in advance for later use, if necessary.

v01d
A: 

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.

Ben Stiglitz