Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Touching custom contentView will close the calloutView #62

Open
guilhermearaujo opened this issue May 16, 2014 · 12 comments
Open

Touching custom contentView will close the calloutView #62

guilhermearaujo opened this issue May 16, 2014 · 12 comments

Comments

@guilhermearaujo
Copy link

Using the "standard" view (with title + subtitle), if I tap the callout view, nothing will happen (or calloutViewClicked will be called, if the delegate is implemented).
However if I set a custom contentView to my calloutView and then tap that custom view, the calloutView won't handle the touch. Instead, the map will dismiss it.

How can I avoid this behavior and prevent the callout view from hiding?

@nfarina
Copy link
Owner

nfarina commented May 16, 2014

So I just tried reproducing this in the sample project - I added a UIButton to the contentView property of the callout - it produced a system-style "Blue link" button that I was able to click without dismissing the callout. I tried with both the UIScrollView container and the MKMapKit container. How is your container view setup?

@guilhermearaujo
Copy link
Author

My custom contentView is just a UIView subclass with four labels. I also added an UIButton as a right accessory view, which works fine and does not dismiss the callout.

http://pastebin.com/k2T8jWgx

I've noticed that if I tap closer to the edges of the callout (and outside the contentView), it works as I expected.

Also using the sample project, I've added to MapKitComparisonController.m, line 104:
self.calloutView.contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
This modification should reproduce the error.

@nfarina
Copy link
Owner

nfarina commented May 16, 2014

Hmm, you're right, that is strange. I suspect this has something to do with MapKit's built-in gesture recognizers. Somehow UIButton is preventing the default recognizers from firing, though I haven't figured out how yet.

@guilhermearaujo
Copy link
Author

@nfarina I found sort of a workaround that should work in all cases where no user interaction is intended to the UIView: simply disable user interaction for the view.

In my case, I have only UILabels, so there's no problem doing that. Unfortunately, if one needs to include a button, or text field, I still don't know what to do.

@akhatmullin
Copy link

I'm try add breakpoint at first line of method 'calloutClicked' in file SMCalloutView.m and this has no effect. This method not calling when I tap calloutView.
While we are waiting solution this issue we can add button at top of our custom contentView. 8)

@nfarina
Copy link
Owner

nfarina commented Apr 7, 2015

Hello, what kind of view is the callout being presented in? Are you using a MKMapView?

@akhatmullin
Copy link

Hello!
I'm using not only MKMapView. GMSMapView using too.
And with GMSMapView all working normally.

I found error. All will be work if we sending our MKMapView in method presentCalloutFromRect:inView:constrainedToView:animated:

@nfarina
Copy link
Owner

nfarina commented Apr 9, 2015

Yes the problem here is that, in those map controls, touch handling is controlled strictly by the map view itself. So you need to do some work to work around that. Check the README for a short discussion on Google's map view, and the Sample project should contain code demoing how to work with MKMapView and receive touches.

@akhatmullin
Copy link

Yes, and we having mistake exactly into Sample project.
This is from file MapKitComparisonController.m (line 115)
[self.calloutView presentCalloutFromRect:annotationView.bounds inView:annotationView constrainedToView:self.view animated:YES];
If we will to present callout as in this example delegate's methods not working.

I using this code into mapView:didSelectAnnotationView::
CLLocationCoordinate2D coordinate = [mapView convertPoint:annotationView.center toCoordinateFromView:mapView];
CGPoint point = [mapView convertCoordinate:coordinate toPointToView:mapView];

CGRect calloutRect = CGRectZero;
calloutRect.origin = point;
calloutRect.size = CGSizeZero;

calloutView.calloutOffset = CGPointMake(0, -annotationView.frame.size.height/2);

[calloutView presentCalloutFromRect:calloutRect inView:mapView constrainedToView:mapView animated:YES];

@hiroara
Copy link

hiroara commented May 27, 2015

Hello. I probably also faced same issue, but it works for me by overriding hitTest:point:withEvent of MKAnnotationView.

I leave writing code but do not know what would be helpful.

// written by swift
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
    return self.subviews.reduce(nil) { hitView, subview in
        if hitView != nil { return hitView }
        let pointForTargetView = subview.convertPoint(point, fromView: self)

        if CGRectContainsPoint(subview.bounds, pointForTargetView) {
            return subview.hitTest(pointForTargetView, withEvent: event)
        } else {
            return nil
        }

    } ?? super.hitTest(point, withEvent: event)
}

I referred to this article.

@lelandrichardson
Copy link

FYI, I ran into this issue and was able to fix the problem by tweaking the following line in the overridden gestureRecognizer:shouldReceiveTouch method on this line:

Essentially:

if ([touch.view isKindOfClass:[UIControl class]])

becomes

if ([touch.view isDescendantOfView:self.calloutView])

@jpm
Copy link

jpm commented Sep 7, 2016

Looks like every year we need another update on this ticket, so here's mine. :)

I also reproduced this issue on my app. To be explicit, we are doing a custom contentView and the calloutViewClicked: delegate method would never fire. The custom contentView has a bunch of labels in it, plus a very small UIButton. Tapping anywhere on the callout would just dismiss it, and the delegate method never fired.

What I did was expose the internal containerView property as readonly:

@property (nonatomic, strong, readonly) UIButton *containerView; // for masking and interaction

Then I modified the example MKMapView subclass:

// override UIGestureRecognizer's delegate method so we can prevent MKMapView's recognizer from firing
// when we interact with UIControl subclasses inside our callout view.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([touch.view isKindOfClass:[UIControl class]])
        return NO;
    else
        return [super gestureRecognizer:gestureRecognizer shouldReceiveTouch:touch];
}

// Allow touches to be sent to our calloutview.
// See this for some discussion of why we need to override this: https://github.com/nfarina/calloutview/pull/9
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *calloutMaybe = [self.calloutView hitTest:[self.calloutView convertPoint:point fromView:self] withEvent:event];
    if ([calloutMaybe isKindOfClass:[UIControl class]]) {
        return calloutMaybe;
    } else if (calloutMaybe) {
        return self.calloutView.containerView;
    }

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

This way if the hit test returns a UIControl subclass, then we pass the event on to it, otherwise we fall back to the main UIButton object which handles the callout taps (aka containerView).

@nfarina Let me know if you would like me to provide a PR for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants