Search code examples
iphonecocoa-touchtouchestouch-event

Detect touch down & touch up in a view containing UIControls


I'm trying to detect when a user touches down and up in a view. The view contains some buttons and other UIKit controls. I would like to detect these touch events but not consume them.

I've tried two approaches, but neither has been sufficient:

First I added a transparent overlay overriding -touchesBegan:withEvent: and -touchesEnded:withEvent: and forward the event to the next responder with

[self.nextResponder touchesBegan:touches withEvent:event]

However, it seems that UIKit objects ignore all forwarded events.

Next, I tried overriding -pointInside:withEvent: and -hitTest:withEvent: . This worked great to detect touch down events, but pointInside:: and hitTest:: are not called on touch up (i.e. [[[event allTouches] anyObject] phase] is never equal to UITouchPhaseEnded ).

What's the best way to detect both touch down and touch up events without disturbing interaction with the underlying UIControls?


Solution

  • according to the Event Handling Guide for iOS you have 3 options:

    1) subclassing UIWindow to override sendEvent:

    2) using an overlay view

    3) designing so that you don't have to do that... so really more like 2 options.

    Here is a simplification of apples example, using UIWindow subclass;

    1) change the class of your window in the NIB to your subclass of UIWindow. 2) put this method in the .m file.

    - (void)sendEvent:(UIEvent *)event
    {
        NSSet * allTouches = [event allTouches];
    
        NSMutableSet *began = nil;
        NSMutableSet *moved = nil;
        NSMutableSet *ended = nil;
        NSMutableSet *cancelled = nil;
    
        // sort the touches by phase so we can handle them similarly to normal event dispatch
        for(UITouch *touch in allTouches) {
            switch ([touch phase]) {
                case UITouchPhaseBegan:
                    if (!began) began = [NSMutableSet set];
                    [began addObject:touch];
                    break;
                case UITouchPhaseMoved:
                    if (!moved) moved = [NSMutableSet set];
                    [moved addObject:touch];
                    break;
                case UITouchPhaseEnded:
                    if (!ended) ended = [NSMutableSet set];
                    [ended addObject:touch];
                    break;
                case UITouchPhaseCancelled:
                    if (!cancelled) cancelled = [NSMutableSet set];
                    [cancelled addObject:touch];
                    break;
                default:
                    break;
            }
    
            // call our methods to handle the touches
            if (began)
            {
                NSLog(@"the following touches began: %@", began);
            };
            if (moved)
            {
                NSLog(@"the following touches were moved: %@", moved);
            };
            if (ended)
            {
                 NSLog(@"the following touches were ended: %@", ended);
            };
            if (cancelled) 
            {
                 NSLog(@"the following touches were cancelled: %@", cancelled);
            };
        }
        [super sendEvent:event];
    }
    

    It has way too much output, but you will get the idea... and can make your logic fit where you want it to.