UIView hitTest:withEvent: and pointInside:withEvent

Rotten picture Rotten · Dec 29, 2011 · Viewed 8.7k times · Source

I'm working with Mixare AR SDK for iOS and I need to solve some bugs that happends, one of them is show the information of a POI when the POI's view is tapped.

Prelude:

Mixare has an overlay UIView within MarkerView views are placed, MarkerView views are moving around the screen to geolocate the POIs and each one has two subviews, an UIImageView and an UILabel.

Issue:

Now, for example, there are 3 visible POIs in the screen, so there are 3 MarkerView as overlay subviews. If you touch anywhere in the overlay, a info view associated to a random POI of which are visible is showed.

Desired:

I want that the associated POI's info is shown only when the user tapped a MarkerView

Let's work. I've see that MarkerView inherits from UIView and implements hitTest:withEvent

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

    viewTouched = (MarkerView*)[super hitTest:point withEvent:event];
    return self;

}

I've put a breakpoint and hitTest is called once for each visible MarkerView but loadedView always is null so I can't work with it, so I've tried to check if the hit point is inside the MarkerView frame implementing pointInside:withEvent: by this way

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    NSLog(@"ClassName: %@", [[self class] description]);
    NSLog(@"Point Inside: %f, %f", point.x, point.y);
    NSLog(@"Frame x: %f y: %f widht:%f height:%f", self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height);

    if (CGRectContainsPoint(self.frame, point))
        return YES;
    else
        return NO;


    return YES;
}

But this function always returns NO, even when I touch the MarkerView. When I check the log I saw that X and Y point values has negative values sometimes and width and height of the view are very small, 0.00022 or similar instead of 100 x 150 that I set the MarkerView frame on its initialization.

Here you are a extract of my log in which you can see the class name, the point and the MarkerView frame values.

ClassName: MarkerView
2011-12-29 13:20:32.679 paisromanico[2996:707] Point Inside: 105.224899, 49.049023
2011-12-29 13:20:32.683 paisromanico[2996:707] Frame x: 187.568573 y: 245.735138 widht:0.021862 height:0.016427

I'm very lost with this issue so any help will be welcome. Thanks in advance for any help provided and I'm sorry about this brick :(

Edit:

At last I've found that the problem is not in hitTest:withEvent: or pointInside:withEvent, problem is with CGTransform that applies to the MarkerView for scaling based on distande and rotating the view, if I comment any code related to this, the Mixare AR SDK works fine, I mean, info view is shown correctly if you touch a marker and doesn't do anything if any other place in the screen is touched.

So, by the moment, I've not solved the problem but I applied a patch removing the CGTransform related code in AugmentedViewController.m class - (void)updateLocations:(NSTimer *)timer function

- (void)updateLocations:(NSTimer *)timer {
    //update locations!

    if (!ar_coordinateViews || ar_coordinateViews.count == 0) {
        return;
    }

    int index = 0;
    NSMutableArray * radarPointValues= [[NSMutableArray alloc]initWithCapacity:[ar_coordinates count]];

    for (PoiItem *item in ar_coordinates) {

        MarkerView *viewToDraw = [ar_coordinateViews objectAtIndex:index];
        viewToDraw.tag = index;

        if ([self viewportContainsCoordinate:item]) {

            CGPoint loc = [self pointInView:ar_overlayView forCoordinate:item];
            CGFloat scaleFactor = 1.5;

            if (self.scaleViewsBasedOnDistance) {
                scaleFactor = 1.0 - self.minimumScaleFactor * (item.radialDistance / self.maximumScaleDistance);
            }

            float width = viewToDraw.bounds.size.width ;//* scaleFactor;
            float height = viewToDraw.bounds.size.height; // * scaleFactor;

            viewToDraw.frame = CGRectMake(loc.x - width / 2.0, loc.y-height / 2.0, width, height);

            /*
            CATransform3D transform = CATransform3DIdentity;

            //set the scale if it needs it.
            if (self.scaleViewsBasedOnDistance) {
                //scale the perspective transform if we have one.
                transform = CATransform3DScale(transform, scaleFactor, scaleFactor, scaleFactor);
            }

            if (self.rotateViewsBasedOnPerspective) {
                transform.m34 = 1.0 / 300.0;

                double itemAzimuth = item.azimuth;
                double centerAzimuth = self.centerCoordinate.azimuth;
                if (itemAzimuth - centerAzimuth > M_PI) centerAzimuth += 2*M_PI;
                if (itemAzimuth - centerAzimuth < -M_PI) itemAzimuth += 2*M_PI;

                double angleDifference = itemAzimuth - centerAzimuth;
                transform = CATransform3DRotate(transform, self.maximumRotationAngle * angleDifference / (VIEWPORT_HEIGHT_RADIANS / 2.0) , 0, 1, 0);
            }            

            viewToDraw.layer.transform = transform;

             */
            //if we don't have a superview, set it up.
            if (!(viewToDraw.superview)) {
                [ar_overlayView addSubview:viewToDraw];
                [ar_overlayView sendSubviewToBack:viewToDraw];
            }
        } else {
            [viewToDraw removeFromSuperview];
            viewToDraw.transform = CGAffineTransformIdentity;
        }
        [radarPointValues addObject:item];
        index++;
    }
    float radius = [[[NSUserDefaults standardUserDefaults] objectForKey:@"radius"] floatValue];
    if(radius <= 0 || radius > 100){
        radius = 5.0;
    }

    radarView.pois = radarPointValues;
    radarView.radius = radius;
    [radarView setNeedsDisplay];
    [radarPointValues release];
}

Any CoreGrapics or UI expert could give us his point of view about this issue??

Answer

orrko picture orrko · Dec 30, 2011

You should either try to hittest as attached:

if ([self pointInside:point withEvent:event]) {
    // do something 
}

I would suggest you add the hit test on the superview, and do the following in the hit test of the parent of the markerViews

if ([markerView pointInside:point withEvent:event]) {
    // extract the tag and show the relevant info
}

Hope this helps