Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TouchesEnded and TouchesCancelled not called

Tags:

ios

uikit

Okay I tested the following with the tabbed application template on Xcode 4.5/iOS 6.

  1. Created a tabbed application.
  2. Created a UIButton subclass called SampleButton and implemented the following mothods:

    - (void)touchesBegan:(NSSet *)touches
               withEvent:(UIEvent *)event
    {
        [super touchesBegan:touches withEvent:event];
    }
    
    - (void)touchesCancelled:(NSSet *)touches
                   withEvent:(UIEvent *)event
    {
        [super touchesCancelled:touches withEvent:event];
    }
    
    - (void) touchesMoved:(NSSet *)touches
                withEvent:(UIEvent *)event
    {
        [super touchesMoved:touches withEvent:event];
    }
    
    - (void)touchesEnded:(NSSet *)touches
               withEvent:(UIEvent *)event
    {
        [super touchesEnded:touches withEvent:event];
    }
    
  3. Added this SampleButton to the first tab.

  4. Added breakpoints to all the touch methods.
  5. Run on device.
  6. Tested that all the touch methods are firing as expected.
  7. Now touch the SampleButton and then also press the second tab.

RESULT: View switches to second tab but touchesCancelled and/or touchesEnded are never called in SampleButton. Shouldn't one or the other of those fire if the view changes while I'm touching that button? This is proving to be a huge issue because, in my app I'm playing a sound while that button is down and it never stops playing if the user switches tabs while pressing it. Seems like this used to work fine in iOS3 and iOS4.

like image 965
Kirby Todd Avatar asked Nov 10 '12 05:11

Kirby Todd


1 Answers

It appears that when a view is removed from its window, it dissociates itself from any touches that were associated with it. So when the touch finally ends, the system doesn't send touchesEnded:… or touchesCancelled:… to the view.

Workaround by disabling tab switching

If you want to just disable tab switching while the button is pressed, you can do that by giving the tab bar controller a delegate and having the delegate return NO from tabBarController:shouldSelectViewController:. For example, in the your test app, you can have FirstViewController make itself the tab bar controller's delegate:

- (void)viewWillAppear:(BOOL)animated {
    self.tabBarController.delegate = self;
}

And the view controller can allow the tab bar controller to select a tab only when the button is not pressed (highlighted):

- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
    return !_button.highlighted;
}

Workaround by detecting button highlight resetting to NO

When the button is removed from its window, it resets its highlighted property to NO. So one generic way to work around this problem is by using key-value observing (KVO) to monitor the button's state (instead of relying on the button to send you actions). Set yourself up as an observer of the button's highlighted property like this:

static int kObserveButtonHighlightContext;

- (void)viewDidLoad {
    [super viewDidLoad];
    [_button addObserver:self forKeyPath:@"highlighted"
        options:NSKeyValueObservingOptionOld
        context:&kObserveButtonHighlightContext];
}

- (void)dealloc {
    [_button removeObserver:self forKeyPath:@"highlighted"
        context:&kObserveButtonHighlightContext];
}

I discovered in testing that the button sends an extra KVO notification when it's removed from the window, before it resets its highlighted property back to NO. So when handling the KVO notification, check that the value has actually changed:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (context == &kObserveButtonHighlightContext) {
        if ([change[NSKeyValueChangeOldKey] boolValue] != _button.highlighted) {
            [self updatePlaybackForButtonState];
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

Finally, start or stop playback according to the highlighted property of the button:

- (void)updatePlaybackForButtonState {
    if (_button.highlighted) {
        NSLog(@"start playback");
    } else {
        NSLog(@"end playback");
    }
}
like image 70
rob mayoff Avatar answered Oct 12 '22 01:10

rob mayoff