29

I'm trying to subclass MKPolyline and MKGeodesicPolyline to store their own individual colours (by having the subclass instances return their own MKPolylineRenderer). It works fine for MKPolyline, but the instances of my MKGeodesicPolyline subclass are not subclasses - simply MKGeodesicPolylines. Can anyone explain why? Here's my code...

protocol MapLineProtocol: MKOverlay {
    var width: CGFloat { get set }
    var colour: UIColor { get set }
}
extension MapLineProtocol {
    var renderer: MKPolylineRenderer {
        let polylineRenderer = MKPolylineRenderer(overlay: self)
        polylineRenderer.strokeColor = self.colour
        polylineRenderer.lineWidth = self.width
        return polylineRenderer
    }
}
class MapLine: MKPolyline, MapLineProtocol {
    var width: CGFloat = 3
    var colour: UIColor = .blue
    convenience init(start: CLLocationCoordinate2D, end: CLLocationCoordinate2D) {
        let line = [start, end]
        self.init(coordinates: line, count: line.count)
    }
}
class MapGeodesic: MKGeodesicPolyline, MapLineProtocol {
    var width: CGFloat = 3
    var colour: UIColor = .red
    convenience init(start: CLLocationCoordinate2D, end: CLLocationCoordinate2D) {
        let line = [start, end]
        self.init(coordinates: line, count: line.count)
    }
}

let mapLine = MapLine(start: loc.coordinate, end: end)
print("Mapline subclass: \(mapLine)") // <Appname.MapLine: xxx>
self.mapView.add(mapLine)
let geoLine = MapGeodesic(start: loc.coordinate, end: end)
print("Geodesic subclass: \(geoLine)") // <MKGeodesicPolyline: xxx> !!!
self.mapView.add(geoLine)

Accessing the .colour property on mapLine is fine (and the renderer works), but accessing .colour on the geoLine causes a run-time exception (and, of course, the renderer doesn't work if you bypass the colour). Can anyone please explain?

Grimxn
  • 22,115
  • 10
  • 72
  • 85
  • What version of swift you are using? I wonder how your code could even compile. – Jonas Sep 08 '17 at 13:07
  • 1
    Swift 4. And indeed, the real question is not how my code compiled, but what trickery does MapKit get up to to break Swift’s type checking so badly that we get a run time rather than compile time error!? – Grimxn Sep 09 '17 at 09:26
  • Did you ever figure out how to do this? I'm seeing the same thing. – Nate Aug 26 '18 at 10:39
  • @Nate - no. Just kludged round it. – Grimxn Aug 26 '18 at 11:07
  • I can't get this to work either in Xcode 12, subclassing with MKGeodesicPolyline makes the object MKGeodesicPolyline and not the subclass. Even if the object conforms to protocols it somehow just... no longer recognises that it conforms to them :| – SparkyRobinson Sep 29 '20 at 05:20
  • related: https://stackoverflow.com/questions/32348942/mkmapviewdelegate-how-to-identifiy-overlay-in-rendererforoverlay – SparkyRobinson Sep 29 '20 at 05:24
  • Definitely related @SparkyRobinson! - I found that 3 years ago when I first came across this, but it's all still a mystery. I guess the answer is that Obj-C "Class Clusters" that we were always warned not to try and mess with or subclass are *really* messy under the hood. – Grimxn Sep 29 '20 at 07:00

2 Answers2

1

As 2023 I have the exact same problem. I solved it by :

Subclassing MKPolyline as MKColorPolyline to have 2 fields, color and isGeodetic : Bool false by default

class MKColorPolyline : MKPolyline {
    var color :  UIColor = .red
    var isGeodesic : Bool = false
    
    convenience init(coordinates: UnsafePointer<CLLocationCoordinate2D>, count: Int, color: UIColor = .red, isGeodesic : Bool = false) {
        self.init(coordinates: coordinates, count: count)
        self.color = color
        self.isGeodesic = isGeodesic
    
        
    }
}

In the code creating the Polyline :

let polyline = MKColorPolyline(coordinates: arc.points.map({$0.coordinate}),
    count: arc.points.count, 
    color:  arc.selected ? .red : map.backTone == .light ? .systemBlue : .systemCyan,
    isGeodesic: true)
view.addOverlay(polyline)

In the delegate

let poly = overlay as! MKColorPolyline
var polylineRenderer : MKPolylineRenderer
                
if poly.isGeodesic{
   let geoPolyline = MKGeodesicPolyline(points: poly.points(), count: poly.pointCount)
   polylineRenderer = MKPolylineRenderer(polyline: geoPolyline)
}else {
    polylineRenderer = MKPolylineRenderer(polyline: overlay as! MKPolyline)
}
                
                
polylineRenderer.fillColor = UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 0.5)
polylineRenderer.strokeColor = (overlay as! MKColorPolyline).color
polylineRenderer.lineWidth = 2.0

0

Am not able to catch exact issue.. but based on my analysis.. MKGeodesicPolyline is inherited from MKPolyline. So when you initialize MKGP will also initialize parent MKP, due to that using the Convenience Init makes some mess I think..

Suresh Thayu
  • 2,132
  • 1
  • 13
  • 8
  • I think that's the right answer. I looked into this problem as well and noticed that this only compiles in Swift 4. (And it should not compile for good reasons) – Jonas Sep 12 '17 at 07:48
  • I'm bemused - why do you say "And it should not compile for good reasons"? What reasons? – Grimxn Mar 16 '21 at 08:47