dynamically change lineWidth of mkpolyline - ios

I have Map in my app in which is moving as per user location.I have successfully drawn a polyline for source and destination.
Using following code
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id < MKOverlay >)overlay{
MKPolylineView *view1 = [[MKPolylineView alloc] initWithOverlay:overlay];
view1.lineWidth = 27.0;
view1.strokeColor = [UIColor colorWithRed:55.0/255.0 green:168.0/255.0 blue:219.0/255.0 alpha:1];
return view1;
}
But my problem is when map is moving , mean i am changing region of map as user moves , then some times polyline is not shown good some time its show thicker than actual size as you can see in below image.
i am attaching the image below, please let me know what can i do for smooth polyline when map is moving.
EDIT
As Matt suggested i create a subclass of MKPolylineRenderer and implement drawMapRect method as below:
-(void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context{
CGMutablePathRef fullPath = CGPathCreateMutable();
BOOL pathIsEmpty = YES;
//merging all the points as entire path
for (int i=0;i< self.polyline.pointCount;i++){
CGPoint point = [self pointForMapPoint:self.polyline.points[i]];
if (pathIsEmpty){
CGPathMoveToPoint(fullPath, nil, point.x, point.y);
pathIsEmpty = NO;
} else {
CGPathAddLineToPoint(fullPath, nil, point.x, point.y);
}
}
//get bounding box out of entire path.
CGRect pointsRect = CGPathGetBoundingBox(fullPath);
CGRect mapRectCG = [self rectForMapRect:mapRect];
//stop any drawing logic, cuz there is no path in current rect.
if (!CGRectIntersectsRect(pointsRect, mapRectCG))return;
UIColor *darker = [UIColor blackColor];
CGFloat baseWidth = 10 / zoomScale;
// draw the dark colour thicker
CGContextAddPath(context, self.path);
CGContextSetStrokeColorWithColor(context, darker.CGColor);
CGContextSetLineWidth(context, baseWidth * 1.5);
CGContextSetLineCap(context, self.lineCap);
CGContextStrokePath(context);
// now draw the stroke color with the regular width
CGContextAddPath(context, self.path);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, baseWidth);
CGContextSetLineCap(context, self.lineCap);
CGContextStrokePath(context);
[super drawMapRect:mapRect zoomScale:zoomScale inContext:context];
}
But Same problem
See below image:
ok, i think i got problem , my polyline is added once, but as user's speed i changing zoom level of MKMapView, zoom level is changed , but polyline width is not refresh,
SO how can i dynamically change lineWidth of mkpolyline ??

The problem is that you are setting your lineWidth at a fixed width (a really big fixed width). This gets larger or smaller as the map is zoomed larger or smaller.
Instead, implement your own MKOverlayRenderer subclass, and override drawMapRect:zoomScale:inContext:. It receives a zoomScale parameter, and so you can adjust the width of your line to look good at what the current scale may be.

Subclass MKPolylineRenderer and override applyStrokePropertiesToContext:atZoomScale: so that it ignores the scale, and draws lines at constant width:
#interface ConstantWidthPolylineRenderer : MKPolylineRenderer
#end
#implementation ConstantWidthPolylineRenderer
- (void)applyStrokePropertiesToContext:(CGContextRef)context
atZoomScale:(MKZoomScale)zoomScale
{
[super applyStrokePropertiesToContext:context atZoomScale:zoomScale];
CGContextSetLineWidth(context, self.lineWidth);
}
#end
Now use it and admire its smooth rendering:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
MKPolyline *polyline = (MKPolyline *)overlay;
ConstantWidthPolylineRenderer *renderer = [[ConstantWidthPolylineRenderer alloc] initWithPolyline:polyline];
renderer.strokeColor = [UIColor redColor];
renderer.lineWidth = 40;
return renderer;
}

You need to use MKPolylineRenderer instead of MKPolylineView
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay {
#try {
MKPolylineRenderer *renderer = [[[MKPolylineRenderer alloc] initWithOverlay:overlay];
renderer.lineWidth = 27.0;
renderer.strokeColor = [UIColor colorWithRed:55.0/255.0 green:168.0/255.0 blue:219.0/255.0 alpha:1];
return renderer;
}
#catch (NSException *exception) {
NSLog(#"exception :%#",exception.debugDescription);
}
}
Read this one:
The MKPolylineRenderer class provides the visual representation for an MKPolyline overlay object. This renderer strokes the line only; it does not fill it. You can change the color and other drawing attributes of the polygon by modifying the properties inherited from the parent class. You typically use this class as is and do not subclass it.
From Apple library
The MKPolylineView class provides the visual representation for an MKPolyline annotation object. This view strokes the path represented by the annotation. (This class does not fill the area enclosed by the path.) You can change the color and other drawing attributes of the path by modifying the properties inherited from the MKOverlayPathView class. This class is typically used as is and not subclassed.
From Apple library

Related

How to Draw the border color for the PolyLine

I need to draw the border for the Polyline on my Mapview
Here is the screenshot that I need to implement but I am getting the poly line without the border.
Code Work:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay {
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay];
renderer.strokeColor = [UIColor rle_lightBurGundy];
renderer.fillColor = [UIColor rle_whiteColor];
renderer.lineWidth = 4.0;
return renderer;
}
Check out this thread Draw MKPolyline Fill Color. Chandni - Systematix' s answer is a good way to solve your issue ... you need to make an custom renderer that will draw 2 paths (the border and the main polyline). He linked a github exemple you can check the ASPolylineRenderer.h/.m
Github link : https://github.com/nighthawk/ASPolylineView

Draw Custom shapes (Geofences) zones on MKMapView

I'm creating an Universal app and I have to create custom safe zones on the map view.
What I do is:
Add a new UIView as superview of map view called squareZone.
To the squareZone view I add UIPanGestureRecognizer, UIPinchGestureRecognizer and UIRotationGestureRecognizer so I can move, rotate and zoom (in and out).
Here is the code of SquareZone
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.opaque = NO;
self.layer.cornerRadius = 5.0f;
}
return self;
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
UIColor *color = [UIColor colorWithRed: 0.765 green: 0.439 blue: 0.443 alpha: 0.658];
UIBezierPath *rectanglePath = [UIBezierPath bezierPathWithRect: rect];
[color setFill];
[rectanglePath fill];
[UIColor.whiteColor setStroke];
rectanglePath.lineWidth = 5;
CGFloat rectanglePattern[] = {2, 3};
[rectanglePath setLineDash: rectanglePattern count: 2 phase: 0];
[rectanglePath stroke];
}
Now, when the user adjust the squareZone I have to show on a UILabel the distance between each point in meters. For that task I'm using
- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location
How can I add/show the four UILabels when the user interacts with the squareZone.
I need some light here. I had seen many tutorials but I cannot imagine how can this is posible. For reference, there is an app called
Trax: https://itunes.apple.com/us/app/trax-gps-tracker/id647170688?mt=8
I have to do the same Drawing Geofence Zone.
Thanks in advance.
First thing to mention is that Trax has two different modes:
1) editing -- where you draw the path
2) live -- where it shows the result.
Editing mode
While in editing, you cannot move and zoom inside of your MKMapView. This happens because they are using the same approach as you do -- they are adding a UIView on top of MKMapView, so that gestures do not conflict with each other.
All you need to do is add CGPoints to some array and use them in the future.
Of course, there are a few difficulties with using CoreGraphics framework, but it is not that tricky.
Live mode
After user added all CGPoints, you now have to convert these points into actual CLLocationCoordinate2D instances.
CGPoint thePoint = ...
CLLocationCoordinate2D theLocationCoordinate2D = [theMapView convertPoint:thePoint toCoordinateFromView:theDrawingView];
What Trax have in their app are probably (almost certainly) instances of MKPolygon class.
You can add it like so:
NSUInteger theCount = self.theMainDrawingView.thePointsArray.count;
CLLocationCoordinate2D theLocationCoordinatesArray[theCount];
for (NSUInteger theIndex = 0; theIndex < theCount; theIndex++)
{
CGPoint thePoint = self.theMainDrawingView.thePointsArray[theIndex].CGPointValue;
CLLocationCoordinate2D theLocationCoordinate2D = [self.theMainMapView convertPoint:thePoint toCoordinateFromView:self.theMainDrawingView];
theLocationCoordinatesArray[theIndex] = theLocationCoordinate2D;
}
MKPolygon *thePolygon = [MKPolygon polygonWithCoordinates:theLocationCoordinatesArray count:theCount];
[self.theMainMapView addOverlay:thePolygon];
However, this is not the end. This will trigger a delegate method of your MKMapView (don't forget to set its .delegate property)
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if (![overlay isKindOfClass:[MKPolygon class]])
{
return nil;
}
MKPolygonView *thePolygonView = [[MKPolygonView alloc] initWithPolygon:(MKPolygon *)overlay];
thePolygonView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
thePolygonView.strokeColor = [[UIColor redColor] colorWithAlphaComponent:0.6];
thePolygonView.lineWidth = 4;
return thePolygonView;
}
Result looks like this:
Summary
Of course, this doesn't fully solve the issue, because you would have to add pinch and pan gestures too, but I hope that I could point into the right direction.

Draw Flag as Overlay on MKMapView

I want to draw a country's flag on top of country boundary. I am using the coordinate data from here
The way I am adding overlay is
[mapView addOverlays:countryName];
Then rendering it as
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonRenderer *renderer = [[MKPolygonRenderer alloc] initWithPolygon:overlay];
renderer.fillColor = [ColorUtil colorFromHexString:#"#EE5A43" withAlpha:0.6];
renderer.strokeColor = [[UIColor whiteColor] colorWithAlphaComponent:1.0];
renderer.lineWidth = 2;
return renderer;
}
return nil;
}
Now my idea was to draw the overlay with the flag. So I subclasses MKPolygonRenderer and added
#implementation MyMKOverlayRenderer
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage {
self = [super initWithOverlay:overlay];
if (self) {
_overlayImage = overlayImage;
}
return self;
}
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
CGImageRef imageReference = self.overlayImage.CGImage;
MKMapRect theMapRect = self.overlay.boundingMapRect;
CGRect theRect = [self rectForMapRect:theMapRect];
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0.0, -theRect.size.height);
CGContextDrawImage(context, theRect, imageReference);
}
#end
Now check the two attached images. I was expecting the flag to be inside the boundary of the polygon. But looks like it is not happening. So may be my approach is wrong. Can some expert help me in correct direction.
I solved this problem. The key was to create a UIBezierPath and clip the overlay image.
I have posted the entire solution here
Now it looks like

How to make stretchable circular region around pin like Reminder App

I tried following code to make circle:
MKCircle *circle = [MKCircle
circleWithCenterCoordinate:userLocation.coordinate radius:1000];
[map addOverlay:circle];
Then in Map View's delegate:
- (MKOverlayView *)mapView:(MKMapView *)map viewForOverlay:(id <MKOverlay>)overlay
{
MKCircleView *circleView = [[MKCircleView alloc] initWithOverlay:overlay];
circleView.strokeColor = [UIColor redColor];
circleView.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.4];
return circleView;
}
It adds a circle around the pin, but how to make the circle stretchable like Reminder App's Location Reminder feature?
Look what I found:
https://github.com/d0ping/DBMapSelectorViewController
You can set up a lot of things like Radius of the circle, Background and Stroke color and even the Radius text.

MkCircle with MapKit, synchronyse with current user location

I am working in a project with MapKit, and I want to update the location of circle in the center of current user location. I can do that by implementing this methods. The problem is that user location when changes its coordinates animates and the method DidUpdateUserLocation doesnt get called everytime.
What I want to do is smoothly changing position with these methods faster and animating the circle like user location (blue dot) changes (dispatch wont do any of this tasks faster)
-(void)viewDidLoad
{
[super viewDidLoad];
[self UpdateCirclePosition];
}
This method get executed when user location changes.
- (void)mapView:(MKMapView *)mv didUpdateUserLocation:(MKUserLocation *)userLocation1
{
[self UpdateCirclePosition];
}
This method is to set new coordinates for the circle
-(void)UpdateCirclePosition
{
//Removing past layouts from MapView
[self.mapView removeOverlays: [self.mapView overlays]];
//Set the circle in the middle of the current user location
MKCircle *circle = [MKCircle circleWithCenterCoordinate:locationManager.location.coordinate radius:10];
[self.mapView addOverlay:circle];
}
And this is the method when overlay changes
- (MKOverlayView *)mapView:(MKMapView *)map viewForOverlay:(id <MKOverlay>)overlay
{
MKCircleView *circleView = [[MKCircleView alloc] initWithOverlay:overlay];
circleView.strokeColor = [UIColor redColor];
if (something)
{
circleView.fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.2];
}
else
{
circleView.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.2];
}
circleView.lineWidth = 0.5;
return circleView;
}
didUpdateUserLocation gets the latest position, but then you ignore it and call UpdateCirclePosition. You should pass the coordinates from userLocation1 into UpdateCirclePosition and use them to reposition your circle.

Resources