IPad cancel button (a-la Keynote and other apps)

In Keynote (and other applications), I noticed that the “standard” interface for running Undo / Redo is to provide a Undo button on the toolbar.

Pressing a button (which is always on) Disables a recent operation. (If there was no last operation to cancel, it will display the Undo / Redo menu).

A long press of the Cancel button opens the Cancel / Redo menu.

I was looking for methods to implement this, and the best answer I have found so far is at the following link .

I wonder if anyone knows an easier way?

Thanks!

+4
source share
4 answers

After looking through all the methods and discussing with friends below, the solution I used is used, since UIBarButtonItem answers both taps and takes a long time to press (TapOrLongPressBarButtonItem).

It is based on the following principles:

  • Subclass UIBarButtonItem
  • Use a custom view (so it’s really trivial to handle a long press - since our user view has no problems with a long click gesture handler ...)

... Until now, this approach has been in a different stream of SO - and I did not like this approach, since I could not find and easily enough create a custom view similar to the iPad navigation bar button ... Soooo ...

Use UIGlossyButton from Water Lou (thanks to the water!). This usage is encapsulated in a subclass ...

The resulting code is as follows:

@protocol TapOrPressButtonDelegate; @interface TapOrPressBarButtonItem : UIBarButtonItem { UIGlossyButton* _tapOrPressButton; __weak id<TapOrPressButtonDelegate> _delegate; } - (id)initWithTitle:(NSString*)title andDelegate:(id<TapOrPressButtonDelegate>)delegate; @end @protocol TapOrPressButtonDelegate<NSObject> - (void)buttonTapped:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem; - (void)buttonLongPressed:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem; @end @implementation TapOrPressBarButtonItem - (void)buttonLongPressed:(UILongPressGestureRecognizer*)gesture { if (gesture.state != UIGestureRecognizerStateBegan) return; if([_delegate respondsToSelector:@selector(buttonLongPressed:withBarButtonItem:)]) { [_delegate buttonLongPressed:_tapOrPressButton withBarButtonItem:self]; } } - (void)buttonTapped:(id)sender { if (sender != _tapOrPressButton) { return; } if([_delegate respondsToSelector:@selector(buttonTapped:withBarButtonItem:)]) { [_delegate buttonTapped:_tapOrPressButton withBarButtonItem:self]; } } - (id)initWithTitle:(NSString*)title andDelegate:(id<TapOrPressButtonDelegate>)delegate { if (self = [super init]) { // Store delegate reference _delegate = delegate; // Create the customm button that will have the iPad-nav-bar-default appearance _tapOrPressButton = [UIGlossyButton buttonWithType:UIButtonTypeCustom]; [_tapOrPressButton setTitle:title forState:UIControlStateNormal]; [_tapOrPressButton setNavigationButtonWithColor:[UIColor colorWithRed:123.0/255 green:130.0/255 blue:139.0/255 alpha:1.0]]; // Calculate width... CGSize labelSize = CGSizeMake(1000, 30); labelSize = [title sizeWithFont:_tapOrPressButton.titleLabel.font constrainedToSize:labelSize lineBreakMode:UILineBreakModeMiddleTruncation]; _tapOrPressButton.frame = CGRectMake(0, 0, labelSize.width+20, 30); // Add a handler for a tap [_tapOrPressButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; // Add a handler for a long-press UILongPressGestureRecognizer* buttonLongPress_ = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(buttonLongPressed:)]; [_tapOrPressButton addGestureRecognizer:buttonLongPress_]; // Set this button as the custom view of the bar item... self.customView = _tapOrPressButton; } return self; } // Safe guards... - (id)initWithImage:(UIImage *)image style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action { NSLog(@"%s not supported!", __FUNCTION__); return nil; } - (id)initWithImage:(UIImage *)image landscapeImagePhone:(UIImage *)landscapeImagePhone style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action { NSLog(@"%s not supported!", __FUNCTION__); return nil; } - (id)initWithTitle:(NSString *)title style:(UIBarButtonItemStyle)style target:(id)target action:(SEL)action { NSLog(@"%s not supported!", __FUNCTION__); return nil; } - (id)initWithBarButtonSystemItem:(UIBarButtonSystemItem)systemItem target:(id)target action:(SEL)action { NSLog(@"%s not supported!", __FUNCTION__); return nil; } - (id)initWithCustomView:(UIView *)customView { NSLog(@"%s not supported!", __FUNCTION__); return nil; } @end 

And all you have to do is:

1. An instant is as follows:

 TapOrPressBarButtonItem* undoMenuButton = [[TapOrPressBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Undo", @"Undo Menu Title") andDelegate:self]; 

2. Connect the button to the navigation bar:

 [self.navigationItem setLeftBarButtonItem:undoMenuButton animated:NO]; 

3. Enter the TapOrPressButtonDelegate protocol, and you're done ...

 -(void)buttonTapped:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem { [self menuItemUndo:barButtonItem]; } -(void)buttonLongPressed:(UIButton*)button withBarButtonItem:(UIBarButtonItem*)barButtonItem { [self undoMenuClicked:barButtonItem]; } 

Hope this helps someone else ...

+4
source

If you use IB (or in the Xcode4 constructor ... I assume that it is being called), then you can select "Cancel" from the first responder and drag this action onto the button. I can give you more specific instructions if this does not apply to him.

Here it looks like Undo Interface Builder image

It is on the left under the “Received Actions” column below.

0
source

I believe the key is in the UINavigationBar itself. Unlike UIButtons or other common touch tracking objects, I suspect that UIBarItems do not handle their own strokes. They do not inherit UIResponder or UIControl methods. However, the UINavigationBar is of course. And I personally added gestures directly to the UINavigationBar many times.

I suggest that you redefine touch processing in a subclass of UINavigationBar and check the bindings to its children. If the baby is your special cancel button, you can handle it accordingly.

0
source
 UIButton* undoButton = [UIButton buttonWithType:UIButtonTypeCustom]; [undoButton addTarget:self action:@selector(undoPressStart:) forControlEvents:UIControlEventTouchDown]; [undoButton addTarget:self action:@selector(undoPressFinish:) forControlEvents:UIControlEventTouchUpInside]; UIBarButtonItem* navButton = [[[UIBarButtonItem alloc] initWithCustomView:undoButton] autorelease]; self.navigationItem.rightBarButtonItem = navButton; 

You do not have to add the UIBarButtonItem as rightBarButtonItem, it is a simple and easy way to show you how to create your UIBarButtonItem with a custom view, which is the UIButton with which you want to handle events.

You will need to execute unodPressStart: and undoPressFinish: by saving state. I would say, at startup, save the current NSDate or some grainy representation of time. If you are done, check if the time has passed, and if it exceeds a certain threshold, show the menu - otherwise (and also, if the start date has never been recorded), cancel.

As an improvement, you will most likely want to also watch the UIControlEventTouchDragExit event to cancel a long press.

0
source

Source: https://habr.com/ru/post/1402584/


All Articles