UIView's -hitTest:withEvent: called three times?
Asked Answered
D

4

13

I am trying to intercept any activity (i.e. touches) that happens inside my whole application.

In other words, I am trying to be notified of any touch event that happens within my main UIView, containing the rest of my controls. To do so, I thought the UIView's method -hitTest:withEvent: was a good solution.

However, when I NSLog into this overriden method before calling [super hitTest:... withEvent:...], I see that it is called 3 times for any touch I make, and I cannot see any difference in the event I receive each time it is called.

Here is how is implemented the method in the main view of my application :

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    NSLog(@"hitTest:withEvent called :");
    NSLog(@"Event: %@", event);
    NSLog(@"Point: %@", NSStringFromCGPoint(point));
    NSLog(@"Event Type: %d", event.type);
    NSLog(@"Event SubType: %d", event.subtype);
    NSLog(@"---");

    return [super hitTest:point withEvent:event];
}

And here is what I NSLog for a single touch in this view :

2010-11-29 14:09:26.892 Application[68818:207] hitTest:withEvent called :
2010-11-29 14:09:26.892 Application[68818:207] Event: <UITouchesEvent: 0x5716d60> timestamp: 37935.2 touches: {(
)}
2010-11-29 14:09:26.892 Application[68818:207] Point: {173, 498}
2010-11-29 14:09:26.892 Application[68818:207] Event Type: 0
2010-11-29 14:09:26.892 Application[68818:207] Event SubType: 0
2010-11-29 14:09:26.893 Application[68818:207] ---
2010-11-29 14:09:26.893 Application[68818:207] hitTest:withEvent called :
2010-11-29 14:09:26.893 Application[68818:207] Event: <UITouchesEvent: 0x5716d60> timestamp: 37935.2 touches: {(
)}
2010-11-29 14:09:26.893 Application[68818:207] Point: {173, 498}
2010-11-29 14:09:26.893 Application[68818:207] Event Type: 0
2010-11-29 14:09:26.893 Application[68818:207] Event SubType: 0
2010-11-29 14:09:26.893 Application[68818:207] ---
2010-11-29 14:09:26.893 Application[68818:207] hitTest:withEvent called :
2010-11-29 14:09:26.894 Application[68818:207] Event: <UITouchesEvent: 0x5716d60> timestamp: 37944.9 touches: {(
)}
2010-11-29 14:09:26.894 Application[68818:207] Point: {173, 498}
2010-11-29 14:09:26.894 Application[68818:207] Event Type: 0
2010-11-29 14:09:26.894 Application[68818:207] Event SubType: 0
2010-11-29 14:09:26.894 Application[68818:207] ---

How could I make any difference between those three notifications in order to trigger the action I want to make only one time for a single touch ?

Thanks in advance !

Desulphurize answered 29/11, 2010 at 13:18 Comment(2)
Ever find a clean solution to this? I'm having the same issue. The time evaluation seems like it would work, but might be easily susceptible to SDK changes in the future.Chesney
any luck on why this happens? i'm trying to figure this out myselfWanderoo
C
10

There are indeed 3 calls to hitTest. It is not clear why, but we can surmise by the timestamps on the event that the first two calls are to do with completing the previous gesture - those timestamps are always very close to whenever the previous touch happened, and will be some distance from the current time.

This is how I determine which hitTest to process:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  NSTimeInterval system = [[NSProcessInfo processInfo] systemUptime];

  if (system - event.timestamp > 0.1) {
    // not the event we were interested in
  } else {
    // use this call
  }
}
Creeper answered 27/12, 2010 at 5:28 Comment(0)
H
8

If this is still a problem for you.

I found several examples and discussions about this topic but the proper solution is quite simple.

In general hittest is called three times within a UIView or a UIScrollView - this results of traversing the view-hierarchy.

A quite simple and for me always suitable solution is:

implement the function hittest in the view you implemented

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

for me - all three function calls have always the same position - so just store the location in your local class.

in the .h File:

@interface MyScrollView : UIScrollView {
    CGPoint touchLocation_;
}

@property (nonatomic, readonly) CGPoint touchLocation;

in the .m File

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    return [super hitTest:point withEvent:event];
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
// The point check you need    
    if(point.y != self.touchLocation.y){
        touchLocation_ = point;
    // The function call you need - just called once.
    }
    return [super pointInside:point withEvent:event];
}

This solution works for me in several projects quite well.

Horotelic answered 4/4, 2012 at 11:17 Comment(0)
R
2

The number of event responses you receive depends on the view hierarchy.

This method traverses the view hierarchy by sending the pointInside:withEvent: message to each subview to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is traversed; otherwise, its branch of the view hierarchy is ignored. You rarely need to call this method yourself, but you might override it to hide touch events from subviews.

This method ignores view objects that are hidden, that have disabled user interaction, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit. Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content.

Points that lie outside the receiver’s bounds are never reported as hits, even if they actually lie within one of the receiver’s subviews. This can occur if the current view’s clipsToBounds property is set to NO and the affected subview extends beyond the view’s bounds.

From the UIView Class Reference.

In a nutshell, if the view you touch has three subviews, and those views are visible and within the bounds of their superview and touch region, you will receive three hit test responses.

Raquel answered 29/11, 2010 at 13:24 Comment(4)
Well, my main view contains more than three subviews, so shouldn't I receive more than 3 responses ? Also, making the same test with a new project containing only 1 view gives me exactly the same results.Desulphurize
Are you using the Simulator or the actual device?Raquel
Hmmm.. I only tested in simulator. I make a try on the real device and come back to youDesulphurize
OK, I tried on the real device, but the result is the same: -hitTest:withEvent: gets called 3 times when I touch down the view.Desulphurize
F
0

This question has been asked for years, but I want to answer it:

The system calls hitTest and pointInside several times to fix the position, and each time the call stack is different, please refer to the official reply from apple.

Fluxmeter answered 5/4, 2019 at 11:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.