3

I'm trying to find a way to consume a button press, not from the response chain per se, but from other action methods bound to that button. I've looked all over for this solution and have been unable to find it.

For example, lets say I set up a button with a selector for an event:

[button addTarget:self action:@selector(handler1:) forControlEvents:UIControlEventTouchUpInside];

Then later in the code, based on specific app circumstances I want to add another event handler for the same control event to the same button:

[button addTarget:self action:@selector(handler2:) forControlEvents:UIControlEventTouchUpInside];

This works fine, both events are indeed called. But my question is, without removing handler1 from the button, how can I make it so that when handler2 is called, the event is "consumed" and handler1 does not get called?

The reason I have such a circumstance is that I want my app to go into a tutorial mode, where I dynamically bind new events to buttons while in the tutorial mode. The tutorial will instruct the user to tap a certain button, but I want the tap events on the other buttons on the screen to be ignored, basically forcing the user to tap the requested button to continue with the tutorial. So every button gets a new TouchUpInside handler added when the user enters the tutorial. I want this new handler to be called first and block the original handler from executing.

I have been able to achieve it being called first by getting all the original events in an NSSet, then calling [button removeTarget...] for all the existing events. Then I add my dynamic event and then re-add all the original events from the set. This works in the debugger to show that my dynamic event is indeed called first.

  • For example:
  • handler1 will do something when pressed (default handler for the button)
  • handler2 is added dynamically and will communicate with the tutorial controller, "consuming" the tap event (preventing handler1 from executing).

When not in tutorial mode, I want handler1 to still do what it's supposed to do, but if handler2 exists I want that method to run, then prevent handler1 from being called. I can't lose handler1 from the button because when the tutorial ends, I'd want the app to work as intended. In addition, I may have certain cases where I still want handler1 to be called.

So, is it possible to consume an event and keep other related events from firing?

I have tried doing a [button resignFirstResponder] in handler2, but that doesn't seem to work. It still calls the original button event handler.

jscs
  • 63,694
  • 13
  • 151
  • 195
zunkination
  • 31
  • 1
  • 5
  • 1
    Why can't you just reset the first handler when the tutorial function of the button is fulfilled? – jscs Oct 02 '14 at 21:27
  • possible duplicate of [UIButton remove all target-actions](http://stackoverflow.com/questions/3340825/uibutton-remove-all-target-actions) – Lefteris Oct 02 '14 at 21:30
  • The app has dozens of buttons. Maybe only one or two of them on a screen would even be involved in the tutorial. If the user presses any of them that aren't involved, I don't want them to do what they are supposed to do by default. But if i just remove the handlers, I'd have to keep track of all of them somewhere and then add them back when the tutorial ends. I could do that, but was looking for a more elegant solution. If it cannot be done, then I will have to choose the brute force technique. :) Thanks for the response. – zunkination Oct 02 '14 at 21:32
  • Another option would be an invisible button covering the regular button that would fulfill the tutorial function. – jscs Oct 02 '14 at 21:35
  • I'm sorry, but this is not a duplicate. I'd like to NOT remove target actions from the buttons. The question is, I want to add a new target to a button and only have that one be called, until such a time that I manually remove that target action. Thanks. – zunkination Oct 02 '14 at 21:36
  • if you clear all previous target actions and add a new one you have what you want. It's so simple. – Lefteris Oct 02 '14 at 21:57
  • Yes, but then I would have to add all those previous actions back at some point, when the tutorial ends. And for the buttons that are involved in the tutorial (e.g. "Hey user, tap this button"), I actually want to keep their default behavior. I'm looking to handle the logic of what buttons comprise the tutorial in a different view controller, and place an order in which they must be pressed. So when the proper button is pressed, the action happens. If the improper button is pressed, nothing happens. When the tutorial ends, the "tutorial actions" are removed and the app works normally. – zunkination Oct 02 '14 at 22:05

3 Answers3

3

Based on the @danielquokka idea about override the method sendActionsForControlEvents:, I subclass UIButton and add following codes. It works fine. After firing event, it will block UI events for 0.5 seconds.

- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    [super sendAction:action to:target forEvent:event];
    self.userInteractionEnabled = NO;

    ///! After handle UIEvent, block this button UI events for a while.
    [self performSelector:@selector(delayEnable) withObject:nil afterDelay:0.5];
}

- (void)delayEnable
{
    self.userInteractionEnabled = YES;
}

UPDATE

Another way to ignore UI events is by [[UIApplication sharedApplication] beginIgnoringInteractionEvents] and [[UIApplication sharedApplication] endIgnoringInteractionEvents].

beginIgnoringInteractionEvents and endIgnoringInteractionEvents will block all touch events for the Application until endIgnoringInteractionEvents called.

Community
  • 1
  • 1
AechoLiu
  • 17,522
  • 9
  • 100
  • 118
1

if you know the current state of the app like tutorial etc then probably better to use only one handler1. And add to the body of the method handler1: if (mode == tutorial) {tutorial} else {default}.

kabarga
  • 803
  • 6
  • 11
  • 1
    I don't think this is a good solution; this rarely-used code doesn't need to be present in the main handler. It will just make things harder to read and maintain. – jscs Oct 02 '14 at 21:57
  • I could do that, but this particular app is a complex rules calculator (used for competition judging). It has multiple screens with dozens of buttons on each screen (which is why we need a tutorial). It would require me to make a lot of modifications that could be avoided with a more elegant higher-level solution. I appreciate the suggestion, and will end up doing something like that if this request is impossible with the iOS framework, but I thought I'd ask here first if there was a better way of doing this. Thanks. – zunkination Oct 02 '14 at 22:00
  • ok. then probably you can add GestureRecognizer and use method ...shouldReceiveTouch. and in this method check if it is your button then return NO; (to cancel handler1), and call handler2 in that place. I did not test it but it could work as a separate solution for tutorial. – kabarga Oct 02 '14 at 22:15
  • Thanks kabarga, that's an intriguing idea that might do exactly what I want. I'll report back... – zunkination Oct 02 '14 at 22:30
1

You may be able to add a category to UIButton (or subclass it), overriding the method sendActionsForControlEvents: to only call sendAction:to:forEvent: for one target (refer to the documentation on UIControl for descriptions of those methods).

danielquokka
  • 861
  • 7
  • 4