Detecting touch gesture on MKPolyline - ios

In my app I have a MapView with a few MKGeodesicPolylines. I want to be able to recognize touch gestures on these lines.
The overlay are added using:
[_mapView addOverlay:polyline];
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay
{
if ([overlay class] == [MKGeodesicPolyline class])
{
MKPolylineRenderer *renderer = [[[MKPolylineRenderer alloc] initWithPolyline:overlay] autorelease];
renderer.lineWidth = 4.0;
renderer.strokeColor = [UIColor blackColor];
return renderer;
}
return nil;
}
I have tried WildcardGestureRecognizer which should fit my purpose. Here is the code I am using:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
.... MapView init ....
WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:_mapView];
CLLocationCoordinate2D coord = [_mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in _mapView.overlays)
{
if ([overlay isKindOfClass:[MKGeodesicPolyline class]])
{
MKGeodesicPolyline *poly = (MKGeodesicPolyline*) overlay;
id view = [_mapView viewForOverlay:poly];
NSLog(#"view class: %#",[view class]);
if ([view isKindOfClass:[MKPolylineRenderer class]])
{
MKPolylineRenderer *polyView = (MKPolylineRenderer*) view;
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon) {
NSLog(#"hit!");
} else {
NSLog(#"miss!");
}
}
}
}
};
[_mapView addGestureRecognizer:tapInterceptor];
}
return self;
}
The problem is the point of the code above where the first Log gets called. The class always seems to return null. Log output:
2014-01-06 13:50:41.106 App[11826:60b] view class: (null)
I hope somebody could point me into the right direction.
Thanks a lot in advance!
WORKING CODE:
I got it working for me. Hope it helps somebody else:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
.... MapView init ....
WildcardGestureRecognizer * tapInterceptor = [[WildcardGestureRecognizer alloc] init];
tapInterceptor.touchesBeganCallback = ^(NSSet * touches, UIEvent * event) {
CGPoint point = [tapInterceptor locationInView:_mapView];
CLLocationCoordinate2D coord = [_mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in _mapView.overlays)
{
if ([overlay isKindOfClass:[MKGeodesicPolyline class]])
{
MKGeodesicPolyline *poly = (MKGeodesicPolyline*) overlay;
id view = [_mapView viewForOverlay:poly];
NSLog(#"view class: %#",[view class]);
if ([view isKindOfClass:[MKPolylineRenderer class]])
{
MKPolylineRenderer *polyView = (MKPolylineRenderer*) view;
[polyView invalidatePath];
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon)
{
NSLog(#"hit!");
} else {
NSLog(#"miss!");
}
}
}
}
};
[_mapView addGestureRecognizer:tapInterceptor];
}
return self;
}
ALTERNATIVE:
Add UITapGestureRecognizer:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[_mapView addGestureRecognizer:recognizer];
[recognizer release];
Handle gesture:
- (void)handleGesture:(UIGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded)
{
for (int i=0; i<recognizer.numberOfTouches; i++)
{
//CGPoint point = [recognizer locationInView:_mapView];
CGPoint point = [recognizer locationOfTouch:i inView:_mapView];
CLLocationCoordinate2D coord = [_mapView convertPoint:point toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coord);
for (id overlay in _mapView.overlays)
{
if ([overlay isKindOfClass:[MKGeodesicPolyline class]])
{
MKGeodesicPolyline *poly = (MKGeodesicPolyline*) overlay;
id view = [_mapView rendererForOverlay:poly];
if ([view isKindOfClass:[MKPolylineRenderer class]] && [[poly title] isEqualToString:#"fullRouteAbove"])
{
MKPolylineRenderer *polyView = (MKPolylineRenderer*) view;
[polyView invalidatePath];
CGPoint polygonViewPoint = [polyView pointForMapPoint:mapPoint];
NSLog(#"polyView: %#",polyView);
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
if (mapCoordinateIsInPolygon)
{
NSLog(#"hit!");
}else{
NSLog(#"miss!");
)
}
}
}
}
}
}
SWITCHING BETWEEN TOUCHABLE AREAS:
Replace
BOOL mapCoordinateIsInPolygon = CGPathContainsPoint(polyView.path, NULL, polygonViewPoint, NO);
with
BOOL mapCoordinateIsInPolygon = CGRectContainsPoint(CGPathGetBoundingBox(polyView.path), polygonViewPoint);
or
BOOL mapCoordinateIsInPolygon = CGRectContainsPoint(CGPathGetPathBoundingBox(polyView.path), polygonViewPoint);

I think you need this:
Add an NSMutableArray called arrPolylineViews to your class.
Then add a tapGestureRecognizer to your mapView:
UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] init];
gr.numberOfTapsRequired = 1;
gr.numberOfTouchesRequired = 1;
gr.delegate = self;
[gr addTarget:self action:#selector(didTap:)];
[myMapView addGestureRecognizer:gr];
In didTap:
- (void)didTap:(UITapGestureRecognizer *)gr
{
if (gr.state == UIGestureRecognizerStateEnded)
{
// convert the touch point to a CLLocationCoordinate & geocode
CGPoint touchPoint = [gr locationInView:myMapView];
MKPolylineView *touchedPolyLineView = [self polylineTapped:touchPoint];
if (touchedPolyLineView)
{
//touched a polyLineView
}
}
}
Then:
- (MKOverlayView*)mapView:(MKMapView*)theMapView viewForOverlay:(id <MKOverlay>)overlay
{
if([overlay isKindOfClass:[MKPolyline class]]){
//create your polyLineView
[arrPolylineViews addObject:polyLineView];
}
}
Then add this method:
- (MKPolylineView *)polylineTapped:(CGPoint)point
{
// Check if the overlay got tapped
for (MKPolylineView *polyView in arrPolylineViews)
{
// Get view frame rect in the mapView's coordinate system
CGRect viewFrameInMapView = [polyView.superview convertRect:polyView.frame toView:myMapView];
// Check if the touch is within the view bounds
if (CGRectContainsPoint(viewFrameInMapView, point))
{
return polyView;
}
}
return nil;
}
Don't forget to -
[arrPolylineViews removeAllObjects];
before adding the new list of points on map.

I got it working using the WildcardGestureRecognizer and UIGestureRecognizer. See my post for code.

Related

How to render the route on MKMapView between two selected pins via long press gesture

I have been working on MapView on how to show routes etc etc.
I have used long press gesture to drop a pin and a polyline is shown between the two pins dropped via long press. Now the polyline which connects the two pins is a straight line, i want to render is properly according to the route on the map. Plz help me.
Heres the code
MapView.h
-(void)viewDidLoad {
[super viewDidLoad];
UILongPressGestureRecognizer *recognizer = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:#selector(addPin:)];
recognizer.minimumPressDuration = 0.5;
[self.mapView2 addGestureRecognizer:recognizer];
}
-(void)addPin:(UIGestureRecognizer *)recognizer {
if (recognizer.state != UIGestureRecognizerStateBegan) {
return;
}
// convert touched position to map coordinate
CGPoint userTouch = [recognizer locationInView:self.mapView2];
CLLocationCoordinate2D mapPoint = [self.mapView2 convertPoint:userTouch toCoordinateFromView:self.mapView2];
NSLog(#"Touched Coord :- %f", mapPoint);
Pin *newPin = [[Pin alloc]initWithCoordinate:mapPoint]; //PIN is NSOBJECT
newPin.title = #"source";
[self.mapView2 addAnnotation:newPin];
[self.allPins addObject:newPin];
[self drawLines:self];
}
- (IBAction)drawLines:(id)sender {
[self drawLineSubroutine];
[self drawLineSubroutine];
}
-(IBAction)undoLastPin:(id)sender {
// grab the last Pin and remove it from our map view
Pin *latestPin = [self.allPins lastObject];
[self.mapView2 removeAnnotation:latestPin];
[self.allPins removeLastObject];
// redraw the polyline
[self drawLines:self];
}
-(void)drawLineSubroutine {
// remove polyline if one exists
[self.mapView2 removeOverlay:self.polyline];
// create an array of coordinates from allPins
CLLocationCoordinate2D coordinates[self.allPins.count];
int i = 0;
for (Pin *currentPin in self.allPins) {
coordinates[i] = currentPin.coordinate;
i++;
}
// create a polyline with all cooridnates
MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coordinates count:self.allPins.count];
[self.mapView2 addOverlay:polyline];
self.polyline = polyline;
// create an MKPolylineView and add it to the map view
self.lineView = [[MKPolylineView alloc]initWithPolyline:self.polyline];
self.lineView.strokeColor = [[UIColor blueColor]colorWithAlphaComponent:0.5];
self.lineView.lineWidth = 7;
// for a laugh: how many polylines are we drawing here?
self.title = [[NSString alloc]initWithFormat:#"%lu", (unsigned long)self.mapView2.overlays.count];
}
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay {
return self.lineView;
}
i am using this one check it might help you
- (void)drawRoute:(NSString *)startLat :(NSString *)startLong :(NSString *)DestLat :(NSString *)DestLong :(NSString *)DestName{ pointArr = malloc(sizeof(CLLocationCoordinate2D) * [TotalRoutes count]);
for(int i = 0; i < [TotalRoutes count]; i++)
{
NSDictionary *route=[TotalRoutes objectAtIndex:i];
pointArr[i]= CLLocationCoordinate2DMake([route[#"Lat"] doubleValue], [route[#"Lng"] doubleValue]) ;
}
myPolyline = [MKPolyline polylineWithCoordinates:pointArr count:TotalRoutes.count];
[_RouteMap addOverlay:myPolyline];
// zooming only First time to polyline
[self zoomToPolyLine:_RouteMap polyline:myPolyline animated:YES];
[self mapView:_RouteMap viewForAnnotation:annotation2];
}
-(void)zoomToPolyLine: (MKMapView*)map polyline: (MKPolyline*)polyline animated: (BOOL)animated
{
[map setVisibleMapRect:[polyline boundingMapRect] edgePadding:UIEdgeInsetsMake(50.0, 50.0, 50.0, 50.0) animated:animated];
}

How to detect an overlay uniquely when more than one overlay added on the MKMapView

In my situation I have to placed more than one polygon as the overlay on the MKMapView, those polygon are actually creating from the JSON response, the response actually containing the polygon_id along with coordinate for the to form that polygon. I just want to some how merge that polygon_id with the overlay, so that whenever user clicked on that overlay it will return the polygon_id.
This is my code:
-(void)darwPolyGon:(NSMutableArray *)polyArr polyGonAreaId:(NSString
*)areaID isAssign:(BOOL)isAssign{
CLLocationCoordinate2D *coordinates =
(CLLocationCoordinate2D*)malloc(sizeof(CLLocationCoordinate2D) *
[polyArr count]);
for (int i=0; i<polyArr.count; i++) {
ModelPolygon *poly=[polyArr objectAtIndex:i];
coordinates[i] = CLLocationCoordinate2DMake(poly.lat,poly.lon);
}
MKPolygon *polygon = [MKPolygon polygonWithCoordinates:coordinates
count:polyArr.count];
strTapAreaId=areaID;
polygon.title=strTapAreaId;
[MyMapView addOverlay:polygon];
}
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id
<MKOverlay>)overlay
{
if([overlay isKindOfClass:[MKPolygon class]]){
MKPolygonView *viewPoly = [[MKPolygonView alloc]
initWithOverlay:overlay];
viewPoly.lineWidth=3;
if (isAssignUser) {
viewPoly.strokeColor=[UIColor colorWithRed:255/255.0f
green:30/255.0f blue:0/255.0f alpha:1.0f];
}else
viewPoly.strokeColor=[UIColor colorWithRed:132/255.0f
green:0/255.0f blue:255/255.0f alpha:1.0f];
viewPoly.tag=[strTapAreaId integerValue];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleMapTap:)];
tap.cancelsTouchesInView = NO;
tap.numberOfTapsRequired = 1;
[MyMapView addGestureRecognizer:tap];
return viewPoly ;
}
return nil;
}
-(void)handleMapTap:(UIGestureRecognizer*)tap{
CGPoint tapPoint = [tap locationInView:MyMapView];
CLLocationCoordinate2D tapCoord = [MyMapView convertPoint:tapPoint
toCoordinateFromView:MyMapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(tapCoord);
CGPoint mapPointAsCGP = CGPointMake(mapPoint.x, mapPoint.y);
for (id<MKOverlay> overlay in MyMapView.overlays) {
if([overlay isKindOfClass:[MKPolygon class]]){
MKPolygon *polygon = (MKPolygon*) overlay;
CGMutablePathRef mpr = CGPathCreateMutable();
MKMapPoint *polygonPoints = polygon.points;
for (int p=0; p < polygon.pointCount; p++){
MKMapPoint mp = polygonPoints[p];
if (p == 0)
CGPathMoveToPoint(mpr, NULL, mp.x, mp.y);
else
CGPathAddLineToPoint(mpr, NULL, mp.x, mp.y);
}
if(CGPathContainsPoint(mpr , NULL, mapPointAsCGP, FALSE)){
MKPolygonView *viewPoly = [[MKPolygonView alloc]
initWithOverlay:overlay];
NSLog(#"tag=%d",viewPoly.tag);
}
CGPathRelease(mpr);
}
}
}
The only methods I could think off are:
1- You can try sneaking in the overlay's ID in its "title" or "subtitle" attribute
2- You can create a class that holds and MKOverlay and its ID and whenever it is selected you can loop over your array of overlays(the ones inside the class) and you add an if statement in case the selected overlay is equal to the overlay in the loop, return its ID

Prevent MKPolygon to have knots

I'm developing an app with a map in which the user can draw a polygon area.
My issue is what it's possible drawing polygons with knots (see the image) (I don't know if knot is the right word). I didn't find a simply way preventing the polygon to get knots.
For the case of the attached image, I would like the small curl to be removed and even the outline to be smoothed
Do you know a way to make that?
The process of drawing the polygon while the user is touching the screen, does use MKPolyline, MKPolygon and MKOverlay as follows:
- (void)touchesBegan:(UITouch*)touch
{
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self.coordinates addObject:[NSValue valueWithMKCoordinate:coordinate]];
}
- (void)touchesMoved:(UITouch*)touch
{
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self.coordinates addObject:[NSValue valueWithMKCoordinate:coordinate]];
}
- (void)touchesEnded:(UITouch*)touch
{
CGPoint location = [touch locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
[self.coordinates addObject:[NSValue valueWithMKCoordinate:coordinate]];
[self didTouchUpInsideDrawButton:nil];
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKOverlayPathView *overlayPathView;
if ([overlay isKindOfClass:[MKPolygon class]])
{
// create a polygonView using polygon_overlay object
overlayPathView = [[MKPolygonView alloc] initWithPolygon:overlay];
overlayPathView.fillColor = [UIColor redColor];
overlayPathView.lineWidth = 1.5;
return overlayPathView;
}
else if ([overlay isKindOfClass:[MKPolyline class]])
{
overlayPathView = [[MKPolylineView alloc] initWithPolyline:(MKPolyline *)overlay];
overlayPathView.fillColor = [UIColor redColor];
overlayPathView.lineWidth = 3;
return overlayPathView;
}
return nil;
}
MKOverlayPathView was deprecated since iOS 7.0. You'd use MKOverlayRenderer instead of it and also related map delegate method.
Try to play with miterLimit property of MKOverlayRenderer.
Example:
-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
if ([overlay isKindOfClass:[MKPolygon class]]) {
MKPolygonRenderer *polygonRenederer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
polygonRenederer.fillColor = [UIColor redColor];
polygonRenederer.lineWidth = 1.5;
polygonRenederer.miterLimit = 10;
return polygonRenederer;
} else if ([overlay isKindOfClass:[MKPolyline class]]) {
MKPolylineRenderer *lineRenderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay];
lineRenderer.strokeColor = [UIColor redColor];
lineRenderer.lineWidth = 3;
return lineRenderer;
}
return nil;
}

KMLViewer in iOS

I am testing Apple's KMLViewer software and I was wondering If I could use it to find in which country x,y coordinates belongs. My KML file has all the data for all countries. (Polygons, overlay..).
If you've already created your MKPolygon overlays, and your MKMapView has created the MKPolygonView views in your mapView:viewForOverlay:, and if you're just trying to see if a tap gesture is in a particular MKPolygonView, I think you can do the following:
- (void)handleTap:(UITapGestureRecognizer *)gesture
{
BOOL success = NO;
CGPoint location = [gesture locationInView:self.mapView];
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:location toCoordinateFromView:self.mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(coordinate);
for (id<MKOverlay> overlay in self.mapView.overlays)
{
MKOverlayView *overlayView = [self.mapView viewForOverlay:overlay];
if ([overlayView isKindOfClass:[MKPolygonView class]])
{
MKPolygon *polygon = (MKPolygon *)overlay;
MKPolygonView *polygonView = (MKPolygonView *)overlayView;
CGPoint polygonViewPoint = [polygonView pointForMapPoint:mapPoint];
if (CGPathContainsPoint([polygonView path], NULL, polygonViewPoint, NO))
{
NSLog(#"Overlay '%#' contains point %#", polygon.title, NSStringFromCGPoint(location));
success = YES;
break;
}
}
}
if (!success)
NSLog(#"No overlays contained point %#", NSStringFromCGPoint(location));
}

Circle Overlay is not showing on map

I am trying to add a circle overlay to a map, but it is not appearing:
- (void) displayOverlayOnMap:(double) lat andlng: (double) lng
{
CLLocationCoordinate2D bostonCoord = CLLocationCoordinate2DMake(lat,lng);
//add MKCircle overlay...
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:1000];
[self.mapView addOverlay:circle];
}
Any body have a clue why it's not showing?
here is an example.. you can also find one using polygons on apple's sites: https://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/LocationAwarenessPG/AnnotatingMaps/AnnotatingMaps.html#//apple_ref/doc/uid/TP40009497-CH6-SW15
or you can use this example
Create overlay from user interaction on MKMapView?
- (void)handleLongPress:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan)
return;
CGPoint touchPoint = [gestureRecognizer locationInView:mapView];
CLLocationCoordinate2D touchMapCoordinate = [mapView convertPoint:touchPoint toCoordinateFromView:mapView];
//add pin where user touched down...
MKPointAnnotation *pa = [[MKPointAnnotation alloc] init];
pa.coordinate = touchMapCoordinate;
pa.title = #"Hello";
[mapView addAnnotation:pa];
[pa release];
//add circle with 5km radius where user touched down...
MKCircle *circle = [MKCircle circleWithCenterCoordinate:touchMapCoordinate radius:5000];
[mapView addOverlay:circle];
}
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
{
MKCircleView* circleView = [[[MKCircleView alloc] initWithOverlay:overlay] autorelease];
circleView.fillColor = [UIColor redColor];
return circleView;
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *AnnotationIdentifier = #"Annotation";
MKPinAnnotationView* pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView)
{
pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
pinView.pinColor = MKPinAnnotationColorGreen;
pinView.animatesDrop = YES;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
Use the code
CLLocationDistance radiusInMeters = 1000;
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:radiusInMeters];
Instead of
MKCircle *circle = [MKCircle circleWithCenterCoordinate:bostonCoord radius:1000];
It worked for me.

Resources