Subclassing MKPolyline (MapKit)

In Swift 3 I'm trying to create a subclass of MapKit's MKPolyline called MKTrack (which I'm planning to give some extra members), but I've problems writing a suitable initializer.


MKPolyline, the superclass, has one designated initializer called init(), which is ultimately inherited from NSObject, and doesn't do a lot. There are two, more useful convenience initializers, which initialize the the polyline with points or coordinates. I would like to base my subclass initializer on one of these. However, the compiler won't let me, since apparentlhy I have to use a designated initializer.


What the compiler does accept and what seems to work is calling two initializers:


class MKTrack: MKPolyline {

.....

convenience init(trackURL: URL) {

self.init()

let parser = Parser()

parser.parseFile(url: trackURL)

self.init(coordinates: &parser.locations, count: parser.locations.count)

}

.....

}


This is a bit weird, in my opinion, calling two initializers, so I'm not convinced that this is the correct approach.

But the compiler doesn't seem to accept anything else, if not calling self.init() and reinventing the already existing code in the convenience initializers of MKPolyline.


Can any one shed some light on this? What am I doing wrong?


Thanks

Replies

I’m not a MapKit expert, so I don’t know what accepted best practice is here, but MKPolyline clearly wasn’t meant to be subclassed. Consider:

  • All of these classes are immutable, so you can’t change their state after they’re initialised

  • MKPolyline exposes state (

    points
    and
    pointCount
    , via the MKMultiPoint parent class) but no initialisers for that state

Note The ‘convenience’ initialises you see for MKPolyline are not actually initialisers. If you look in the Objective-C header, you’ll see that they’re actually constructor class methods.

+ (instancetype)polylineWithPoints:(const MKMapPoint *)points count:(NSUInteger)count;
+ (instancetype)polylineWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count;

Thus, it’s going to e hard to safely subclass MKPolyline here. The only legal thing to do is:

  1. Add your own designated initialiser

  2. Have it call

    -init
  3. Override the

    points
    and
    pointCount
    getters to return your point data.

And then you have to hope that every method internal to MKPolyline accesses this point data via these properties, which seems unlikely to me.

Finally, consider what happens if you have a subclass (MyPolyline) and someone calls

+polylineWithPoints:count:
on it. How does the supplied point data make it to your state?

I took a look at

+polylineWithPoints:count:
and it turns out that it allocates an instance of
[self class]
and then calls a private setter on it )-:

I don’t think you’re going to get answers in the ‘Swift space’ here; you’ll need to consult with MapKit experts to see if subclassing MKPolyline is supported and, if so, how it should be done. I recommend you ask over in System Frameworks > Maps and Location. If that doesn’t pan out, you should ask for formal support via a DTS tech support incident.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Apparently! I think the easies solution will be, unlesss someone else finds a better way, to create my own class based on NSObject, encapsulating an MKPolyLine object.


Thanks!

Hello Janco,

First a caveat, this is code I haven't touched in a few months. It is from an older version of Swift. I will be taking it up again in a few months. But I don't see anything illegal about it. Here is an applicable snippet:


protocol Shape
  {
  var layer : Layer! { get set }
  var feature : GDAL.Feature! { get set }
  }
// An MKPolyLine that knows how to render itself.
class LineString : MKPolyline, Shape
  {
  var layer : Layer!
  var feature : GDAL.Feature!
  var renderer : MKPolylineRenderer?

  override init()
    {
    super.init()
    }
  // Constructor with GDAL geometry and parent feature.
  static func create(
    geometry: GDAL.Geometry, feature: GDAL.Feature, layer: Layer)
      -> LineString
    {
    var points = pointsFromGeometry(geometry)

    let lineString = LineString(coordinates: &points, count: points.count)

    lineString.layer = layer
    lineString.feature = feature

    lineString.title = "LineString"
    lineString.subtitle = "Shape"

    return lineString
    }
  }


Alas, Apple's code formatted seems to eat comments...


My concern with your code is that you've got some kind of parser inside the constructor that seems to be making a networking call. I strongly suggest you do that on some kind of async queue and construct these objects after the data has arrived and been successfully parsed.


Don't discount Eskimo's caution about subclassing these classes. These days, I am extremely skittish about using any Apple code. Apple can and will change it according to its own needs and schedule. When I re-do this code, I expect to do it very differently. Most likely, I will do the next version in pure Objective-C++ and use C++ wrappers to link my classes to Apple's. If you have the resources to provide your own base maps, I suggest using WhirlyGlobe/Maply (http://mousebird.github.io/WhirlyGlobe/) instead.