Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to customize MKPolyLineView to draw different style lines

I want to customize the lines drawn on MKMapView to show a route so that the lines have a border color and a fill color. Similar to this where it has a black border and is filled with another color:

blue line with black border

I'm currently just returning MKPolyLineView objects from mapView:viewForOverlay: which works fine for plain lines. The docs says the MKPolyLineView is not to be subclassed, so should I subclass MKOverlayView and implement my own drawMapRect? Or should I subclass MKOverlayPathView? Or create a replacement for MKPolylineView?

EDIT - what I'm asking is: where is the place to put your own Quartz drawing code in order to draw your own annotations/overlays? Currently I've created a subclass of MKOverlayView and implement my own drawMapRect:zoomScale:inContext: It's pretty easy to draw the overlay that way but is that the best solution?

like image 269
progrmr Avatar asked Oct 14 '11 12:10

progrmr


5 Answers

You can do this by implementing your own MKOverlayPathView subclass, which draws the path twice in the map rect. Once thicker with black and once thinner on top with another colour.

I have created a simple drop-in replacement of MKPolylineView which lets you do that: ASPolylineView.

If you want to do it yourself, the two main methods that you need to implement could look like this:

- (void)drawMapRect:(MKMapRect)mapRect
          zoomScale:(MKZoomScale)zoomScale
          inContext:(CGContextRef)context
{
    UIColor *darker = [UIColor blackColor];
    CGFloat baseWidth = self.lineWidth / 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, self.strokeColor.CGColor);
    CGContextSetLineWidth(context, baseWidth);
    CGContextSetLineCap(context, self.lineCap);
    CGContextStrokePath(context);

    [super drawMapRect:mapRect zoomScale:zoomScale inContext:context];
}

- (void)createPath
{
    // turn the polyline into a path

    CGMutablePathRef path = CGPathCreateMutable();
    BOOL pathIsEmpty = YES;

    for (int i = 0; i < self.polyline.pointCount; i++) {
        CGPoint point = [self pointForMapPoint:self.polyline.points[i]];

        if (pathIsEmpty) {
            CGPathMoveToPoint(path, nil, point.x, point.y);
            pathIsEmpty = NO;
        } else {
            CGPathAddLineToPoint(path, nil, point.x, point.y);
        }
    }

    self.path = path;
}
like image 181
Adrian Schönig Avatar answered Nov 14 '22 07:11

Adrian Schönig


You can just add two MKPolyLineView objects with the same coordinates, but different thicknesses.

Add one with a lineWidth of 10 (or whatever) with strokeColor set to black.

Then add another with a lineWidth of 6 with strokeColor set to your other desired color.

You can use the same MKPolyLine for both MKPolyLineView objects.

like image 38
MindJuice Avatar answered Nov 14 '22 09:11

MindJuice


MKPolylineView can only be used for stroking a designated path. You can use some of the properties in MKOverlayPathView to change their appearance but only some of them would apply, e.g. fillColor, strokeColor.

If you want to draw something more complex, you can use MKOverlayPathView. It is more generic and thus suited for more than just stroking paths. For drawing simple lines, the result would be identical to MKPolylineView (at least, according to the docs).

If you want to do more complex drawing, subclass MKOverlayPathView. What you're trying to do is non-trivial.

like image 2
Nick Toumpelis Avatar answered Nov 14 '22 08:11

Nick Toumpelis


I use a subclass NamedOverlay that holds an overlay an a name:

NamedOverlay.h

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface NamedOverlay : NSObject <MKOverlay>

@property (strong, readonly, nonatomic) NSString *name;
@property (strong, readonly, nonatomic) id<MKOverlay> overlay;

-(id)initWithOverlay:(id<MKOverlay>)overlay andName:(NSString *)name;

@end

NamedOverlay.m

#import "NamedOverlay.h"

@implementation NamedOverlay

- (id)initWithOverlay:(id<MKOverlay>)overlay andName:(NSString *)name
{
    _name = name;
    _overlay = overlay;
    return self;
}

- (MKMapRect)boundingMapRect
{
    return [_overlay boundingMapRect];
}

- (CLLocationCoordinate2D)coordinate
{
    return [_overlay coordinate];
}

-(BOOL)intersectsMapRect:(MKMapRect)mapRect
{
    return [_overlay intersectsMapRect:mapRect];
}

@end

and in the map controller I instantiate two overlays with different name, then in the MKMapViewDelegate I can identify which overlay I want to draw and do something like:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id < MKOverlay >)overlay
{
    NamedOverlay *namedOverlay = (NamedOverlay *) overlay;
    MKPolyline *polyline = namedOverlay.overlay;
    if ([namedOverlay.name isEqualToString:@"top"]) {
        MKPolylineView *view1 = [[MKPolylineView alloc] initWithOverlay:polyline];
        view1.strokeColor = [UIColor whiteColor];
        view1.lineWidth = 25.0;
        return view1;
    } else {
        MKPolylineView *view1 = [[MKPolylineView alloc] initWithOverlay:polyline];
        view1.strokeColor = [UIColor blueColor];
        view1.lineWidth = 15.0;
        return view1;
    }
}
like image 2
user2704438 Avatar answered Nov 14 '22 09:11

user2704438


I know that this may not match the pure approach you want, but why not using MKPolygon instead of a MKPolyLine ?
Create a MKPolygon instance that represents a kind of corridor around your route, and then , when you create the MKPolygonView that corresponds to the MKPolygon/corridor you've created, set the properties of the MKPolygonView to get a different fill color and strokeColor

  myPolygonView.lineWidth=3;
  myPolygonView.fillColor=[UIColor blueColor];
  myPolygonView.strokeColor=[UIColor darkGrayColor];

I didn't try it myself but this should work. Only drawback is that when you zoom in / out, the 'width' of the 'route' will change.... :/

like image 1
yonel Avatar answered Nov 14 '22 07:11

yonel