Gestures to subviews & MKMapView in iOS - basic iO

2019-06-14 04:28发布

问题:

I have a following setup:

 mainViewController
    OverlayView - UIView
    mapView - MKMapView

My OverlayView is shown over mapView and responds to UIPanGestureRecognizer

Now because OverlayView is above MapView, I can't get the pinch to zoom functionality of MapView to work..

What do I have to do to get mapView to react to pinch and OverLayView to react to Pan (as it does now)?

The solution I have now is just implementing the pinch2zoom functionality in the mainViewController so that it scales mapView accordingly, but it is way less smooth than the original Apple's implementation.

回答1:

If you have a view that is supposed to be in front of the MKMapView and not move on the screen as the map moves underneath it, you should implement it as you've described, as a separate view in front of (i.e. on top of) the map view. But rather than having this front view handle gestures and try to change the map view programmatically, you should just set the userInteractionEnabled to NO for this front view (you can do this programmatically or via Interface Builder). This will let the map view behind it receive the touches.

If you have some controls on that front view which need to accept user interaction, then go ahead and enable user interaction for those few controls, but make sure that the bulk of this front view is configured to not have userInteractionEnabled.


If you wanted an overlay that should move with the map, you should just add the overlay to the MKMapView, itself, not a separate view. See Displaying Overlays on a Map in the Location Awareness Programming Guide. If you use MKMapView overlays instead of a separate view, you don't lose any of the built-in gestures.

For example, if you set the delegate for your MKMapView to be your view controller, you can then write a rendererForOverlay in iOS 7 (or viewForOverlay method for earlier versions):

// for iOS7+; see `viewForOverlay` for earlier versions

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
    if ([overlay isKindOfClass:[MKPolygon class]])
    {
        MKPolygonRenderer *renderer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];

        renderer.fillColor   = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
        renderer.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
        renderer.lineWidth   = 3;

        return renderer;
    }

    if ([overlay isKindOfClass:[MKCircle class]])
    {
        MKCircleRenderer *renderer = [[MKCircleRenderer alloc] initWithCircle:overlay];

        renderer.fillColor   = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
        renderer.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
        renderer.lineWidth   = 3;

        return renderer;
    }

    if ([overlay isKindOfClass:[MKPolyline class]])
    {
        MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay];

        renderer.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
        renderer.lineWidth   = 3;

        return renderer;
    }

    return nil;
}

// for iOS versions prior to 7; see `rendererForOverlay` for iOS7 and later

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
    if ([overlay isKindOfClass:[MKPolygon class]])
    {
        MKPolygonView *overlayView = [[MKPolygonView alloc] initWithPolygon:overlay];

        overlayView.fillColor      = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
        overlayView.strokeColor    = [[UIColor blueColor] colorWithAlphaComponent:0.7];
        overlayView.lineWidth      = 3;

        return overlayView;
    }

    if ([overlay isKindOfClass:[MKCircle class]])
    {
        MKCircleView *overlayView = [[MKCircleView alloc] initWithCircle:overlay];

        overlayView.fillColor     = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
        overlayView.strokeColor   = [[UIColor blueColor] colorWithAlphaComponent:0.7];
        overlayView.lineWidth     = 3;

        return overlayView;
    }

    if ([overlay isKindOfClass:[MKPolyline class]])
    {
        MKPolylineView *overlayView = [[MKPolylineView alloc] initWithPolyline:overlay];

        overlayView.strokeColor     = [[UIColor blueColor] colorWithAlphaComponent:0.7];
        overlayView.lineWidth       = 3;

        return overlayView;
    }

    return nil;
}

This handles polygons, circles, and lines. Clearly if you're only drawing polygons, for example, you could simplify the above code accordingly.

Once you do that, you can now add an overlay directly to the map. For example, this adds an overlay that is a rectangle of a certain size around a particular coordinate:

- (void)addOverlayAround:(CLLocationCoordinate2D)originalCoordinate atDistance:(double)distanceKm
{
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(originalCoordinate, distanceKm * 1000.0 * 2.0, distanceKm * 1000 * 2.0);
    MKCoordinateSpan span = region.span;

    CLLocationCoordinate2D  points[4];
    points[0] = CLLocationCoordinate2DMake(originalCoordinate.latitude  + span.latitudeDelta / 2.0,
                                           originalCoordinate.longitude - span.longitudeDelta / 2.0);
    points[1] = CLLocationCoordinate2DMake(originalCoordinate.latitude  + span.latitudeDelta / 2.0 ,
                                           originalCoordinate.longitude + span.longitudeDelta / 2.0);
    points[2] = CLLocationCoordinate2DMake(originalCoordinate.latitude  - span.latitudeDelta / 2.0,
                                           originalCoordinate.longitude + span.longitudeDelta / 2.0);
    points[3] = CLLocationCoordinate2DMake(originalCoordinate.latitude  - span.latitudeDelta / 2.0,
                                           originalCoordinate.longitude - span.longitudeDelta / 2.0);

    MKPolygon* poly = [MKPolygon polygonWithCoordinates:points count:4];
    if ([self.mapView respondsToSelector:@selector(addOverlay:level:)])
        [self.mapView addOverlay:poly level:MKOverlayLevelAboveLabels];
    else
        [self.mapView addOverlay:poly];

//    // If you want to draw a circle around the coordinate, instead, you could do something like:
//
//    MKCircle *circle = [MKCircle circleWithCenterCoordinate:originalCoordinate radius:distanceKm * 1000.0 * sqrt(2.0)];
//    if ([self.mapView respondsToSelector:@selector(addOverlay:level:)])
//        [self.mapView addOverlay:circle level:MKOverlayLevelAboveLabels];
//    else
//        [self.mapView addOverlay:circle];

//    // if you want to draw some lines, you could do something like:
//  
//    MKPolyline *polyline = [MKPolyline polylineWithCoordinates:points count:4];
//    if ([self.mapView respondsToSelector:@selector(addOverlay:level:)])
//        [self.mapView addOverlay:polyline level:MKOverlayLevelAboveLabels];
//    else
//        [self.mapView addOverlay:polyline];

    self.mapView.delegate = self;
}


回答2:

I don't actually now if what I'm going to propose can work or if is a good solution, anyway that is the idea

Set your MainViewController as delegate for the gesture recognizers

-(BOOL)gestureRecognizer:(UIGestureRecognizer*)gr shouldReceiveTouch:(UITouch*)touch{
    if([gr isKindOfClass:[UIPanGestureRecognizer class]])
        [mapView setUserInteractionEnabled:NO];
    else if([gr isKindOfClass:[UIPinchGestureRecognizer class]])
        [overlayView setUserInteractionEnabled:YES];
    return YES;
}