9

I'm building a MapKit based app for iPhone.

I have a number of MKPolylines added to the map.

However, instead of having a MKPolyline, I would like to have my own Model class conforming to the MKOverlay protocol added to the map so that I can access the model properties when creating the corresponding view in mapView:viewForOverlay.

The problem is that I can't find the way to inherit from MKPolyline because it doesn't have any init methods that I can call from the subclass' init. You can only create them using the convenience methods.

How can I bring together the model properties and the MKPolyline behaviour?

JAL
  • 41,701
  • 23
  • 172
  • 300
tato
  • 5,439
  • 4
  • 31
  • 27

6 Answers6

7

MANIAK_dobrii's code is the way to go but I found I had to implement some additional MKMultiPoint methods to get it to work, here are my complete header and implementation files for an AnchorLine class I used:-

Header AnchorLine.h

#import <MapKit/MapKit.h>

@interface AnchorLine : NSObject <MKOverlay> {
    MKPolyline* polyline;
}

@property (nonatomic, retain) MKPolyline* polyline;

+ (AnchorLine*)initWithPolyline: (MKPolyline*) line;
@end

Implementation AnchorLine.m

#import "AnchorLine.h"

@implementation AnchorLine

@synthesize polyline;


+ (AnchorLine*)initWithPolyline: (MKPolyline*) line {
    AnchorLine* anchorLine = [[AnchorLine alloc] init];
    anchorLine.polyline = line;
    return [anchorLine autorelease];
}

- (void) dealloc {
    [polyline release];
    polyline = nil;
    [super dealloc];
}

#pragma mark MKOverlay
//@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
- (CLLocationCoordinate2D) coordinate {
    return [polyline coordinate];
}

//@property (nonatomic, readonly) MKMapRect boundingMapRect;
- (MKMapRect) boundingMapRect {
    return [polyline boundingMapRect];
}

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

- (MKMapPoint *) points {
    return [polyline points];
}


-(NSUInteger) pointCount {
    return [polyline pointCount];
}

- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range {
    return [polyline getCoordinates:coords range:range];
}

@end

Hope that helps someone.

Perception
  • 79,279
  • 19
  • 185
  • 195
goelectric
  • 320
  • 3
  • 10
4

You can set an associated object attribute of the class. This allows you to bind an instance variable to an existing class. Make sure you properly clean up after yourself.

Community
  • 1
  • 1
Wayne Hartman
  • 18,369
  • 7
  • 84
  • 116
4

I rewrote jmathew's excellent answer which I didn't find anywhere else, and it works like charm. A little modified version in Swift:

final class CustomPolyline: MKPolyline {
    private(set) var color: UIColor?
    private(set) var width: CGFloat?

    convenience init(coordinates: [CLLocationCoordinate2D], color: UIColor, width: CGFloat) {
        self.init(coordinates: coordinates, count: coordinates.count)
        self.color = color
        self.width = width
    }
}
jason d
  • 416
  • 1
  • 5
  • 10
2

UPDATE: There's another option (could be better) to use message forwarding for this (like -forwardingTargetForSelector or stuff).

I had the same issue today but came up with other solution. Instead of using suggested by Wayne associated object attribute stuff I just encapsulated MKPolyline in another class and transferred MKOverlay protocol's messages to it.

So I've got something like in .h:

@interface MyOverlay : NSObject <MKOverlay>
{
    MKPolyline* polyline;
    id object;
}

@property (nonatomic, retain) id object;
@property (nonatomic, retain) MKPolyline* polyline;

+ (MyOverlay*)myOverlayWithObject: (id)anObject;

@end

And in .m:

@implementation MyOverlay
@synthesize object;
@synthesize polyline;


+ (MyOverlay*)routePartOverlayWithObject: (id)anObject {    

    MyOverlay* myOverlay = [[MyOverlay alloc] init];

    ... generating MKPolyline ...

    myOverlay.polyline = ... generated polyline ...;
    routePartOverlay.object = anObject;


    return [myOverlay autorelease];
}

- (void) dealloc {
    [cdRoutePart release]; cdRoutePart = nil;
    [polyline release]; polyline = nil;

    [super dealloc];
}

#pragma mark MKOverlay
//@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
- (CLLocationCoordinate2D) coordinate {
    return [polyline coordinate];
}

//@property (nonatomic, readonly) MKMapRect boundingMapRect;
- (MKMapRect) boundingMapRect {
    return [polyline boundingMapRect];
}

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

@end

So MyOverlay behaves like MKPolyline (conforms to MKOverlay) and at the same time I can do anything with it, having as many properties as I need.

MANIAK_dobrii
  • 6,014
  • 3
  • 28
  • 55
2

It is true that MKPolyline doesn't have its own init method. In fact the only class in MKPolyline's inheritance chain that does have an init method is NSObject.

So when I subclassed MKPolyline I just overrode the init method defined by NSObject...

-(id) init {
    self = [super init];
    if(self) {
        //my initialization here
    }
    return self;
}

Then when you want to instantiate your subclass with coordinates you might do something like this...

-MyPolyline* myPolyline = (MyPolyline*)[MyPolyline polylineWithCoordinates:coordinates count:coordinateCount];
Ed LaFave
  • 399
  • 3
  • 11
  • Problem with that is that there is then no way to set the coordinates, as coordinates is a readonly property and can only be set by the convenience method. I too was hoping to subclass MKPolyline, just to add a piece of information to it, but it appears I can't do this. – GendoIkari Mar 03 '11 at 23:16
  • There is a way to set the coordinates on a subclass of MKPolyline, here is a code snippet... MyPolyline* myPolyline = (MyPolyline*)[MyPolyline polylineWithCoordinates:coordinates count:coordinateCount]; – Ed LaFave Mar 04 '11 at 14:25
  • 3
    polylineWithCoordinates always returns an MKPolyline... it won't ever return a MyPolyline. So even if you cast it like that, all you are doing is telling the compiler that it is a MyPolyline. In memory it will still actually be an MKPolyline. – GendoIkari Mar 04 '11 at 15:30
  • All I can tell you is that this is the code I'm using and it works. I've added data members to the MyPolyline class and I'm able to read and set them without a problem. – Ed LaFave Mar 04 '11 at 17:39
  • 1
    with that code, you are replacing your existing object reference with a reference to another new object (an MKPolyline). Gendolkari is right, it shouldn't work. And if it does, it's just a matter of coincidence. It will stop working at the worst moment. – tato Mar 04 '11 at 22:56
  • convenience methods also inherit from MKPloyLine, so you can alloc correct memory. very good. – user501836 Apr 19 '12 at 04:07
1

Whats mentioned here so far hasn't quite worked for me but I managed a solution based on the other answers and some independent research. I am not 100% certain in this but you can cast a MKPolyline into a custom sub-class only if you use the static method call that calls the right 'init' method internally.

(CustomPolyline*)[CustomPolyline polylineWithCoordinates:coordinates count:coordinateCount]

The above won't work because polylineWithCoordinates only allocates memory for an MKPolyline object and not CustomPolyline. I suspect what's happening internally is that polylineWithCoordinates calls another initializer method in a manner similar to: [MKPolyline otherInitMethod:...]. And its not allocating the proper amount of memory because its now using an MKPolyline static method call and not our CustomPolyline static call.

However if we use

(CustomPolyline*)[CustomPolyline polylineWithPoints:polyline.points count:polyline.pointCount];

It does work. I think this is because polylineWithPoints is using an initializer that returns an id not just chaining to another method call. And since we called it using the CustomPolyline class the initializer allocates memory for CustomPolyline not MKPolyline.

I could be completely wrong on why it works. But I've tested this and it seems to work fine. MKPolygon can be extended in a similar manner. In that case I think the correct static method to use is MKPolygon polygonWithCoordinates:points count:pointSet.count]]

My implementation for reference:

CustomPolyline.h

#import <MapKit/MapKit.h>

typedef enum {
    CustomPolylineTypeNone = 0,
    CustomPolylineDifferentStrokes
} CustomPolylineType;

/**
 * CustomPolyline wraps MKPolyline with additional information about a polyline useful for differentiation.
 */
@interface CustomPolyline : MKPolyline

@property CustomPolylineType type;

-(CustomPolyline*)initWithMKPolyline:(MKPolyline*)polyline;

@end

CustomPolyline.m

#import "CustomPolyline.h"

@implementation CustomPolyline

@synthesize type;

/**
 * Takes an MKPolyline and uses its attributes to create a new CustomPolyline
 */
-(CustomPolyline*)initWithMKPolyline:(MKPolyline*)polyline
{
    // We must use the this specific class function in this manner to generate an actual
    // CustomPolyline object as opposed to a MKPolyline by a different name
    return (CustomPolyline*)[CustomPolyline polylineWithPoints:polyline.points count:polyline.pointCount];
}

@end
jmathew
  • 1,522
  • 17
  • 29