Reputation: 211
I am developing a navigation application that displays custom tiles and multicolour polylines on the map. When the map moves the polylines and the custom tiles break on the edge of the tiles. When the map is refreshed by the system the issue is solved, but this refresh is usually slow and the broken lines are visible on the screen for a couple of seconds.
Here you can see the issue. Normally the polyline's width is the thinner one, but sometimes the map draws the lines wider and then it stays on the screen until the next refresh.
(As you can see this is a CarPlay application but the issue tends the appear on a normal app.)
I use a custom class inherited from MKPolylineRenderer
as this polyline has different colours based on some logic.
Like here:
Has anyone an idea what could case the issue and how to resolve it?
My code for the renderer class:
import MapKit
import Foundation
class CustomGradientPolylineRenderer: MKPolylineRenderer {
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
let boundingBox = self.path.boundingBox
let mapRectCG = rect(for: mapRect)
if(!mapRectCG.intersects(boundingBox)) { return }
guard let polyLine = self.polyline as? GradientRoutePolyline else { return }
context.setLineCap(.round)
drawOneColorLine(on: context, lineWidth: (lineWidth - 1) / zoomScale, color: .white)
drawGradientLine(on: context, lineWidth: (lineWidth - 6) / zoomScale, colors: polyLine.colors)
super.draw(mapRect, zoomScale: zoomScale, in: context)
}
private func drawOneColorLine(on context: CGContext, lineWidth: Double, color: UIColor) {
context.setLineWidth(lineWidth)
let points = self.polyline.points()
context.move(to: self.point(for: points[0]))
for index in 1...self.polyline.pointCount - 1 {
context.addLine(to: self.point(for: points[index]))
}
context.setStrokeColor(color.cgColor)
context.strokePath()
}
private func drawGradientLine(on context: CGContext, lineWidth: Double, colors: [UIColor]) {
context.setLineWidth(lineWidth)
for index in 1...self.polyline.pointCount - 1 {
let point = self.point(for: self.polyline.points()[index])
let prevPoint = self.point(for: self.polyline.points()[index - 1])
context.move(to: prevPoint)
context.addLine(to: point)
let currentColor = colors[index].cgColor
context.setStrokeColor(currentColor)
context.strokePath()
}
}
}
The GradientRoutePolyline
class:
import MapKit
import Foundation
/// Inherits from MKPolyline and it represents the route which is drawn on the map as a polyline. It must be rendered with CustomGradientPolylineRenderer as it's color changes based on the car's charge level.
class GradientRoutePolyline: MKPolyline {
var id: Int = 0
var lineWidth: CGFloat = 0.0
var colors: [UIColor] = []
var locations: [CGFloat] = []
/// Initializer for the class. Computes the different colors used on the route based on the RoutePoint array received as parameter. Then calls the default init with coordinates.
/// - Parameters:
/// - routePoints: RoutePoints that hold the coordinates for the route and the inforamtion about the vehicle charge level for calculating colors.
/// - lineWidth: The line width to use.
convenience init(routePoints: [RoutePoint], lineWidth: CGFloat) {
let pointArray = routePoints.map {
CLLocationCoordinate2D(latitude: $0.location.latitude, longitude: $0.location.longitude)
}
self.init(coordinates: pointArray, count: pointArray.count)
self.lineWidth = lineWidth
self.colors = routePoints.map {
.init(red: 1 - $0.soC, green: $0.soC, blue: 0, alpha: 1)
}
self.locations = Array(0..<colors.count).map { CGFloat($0) / CGFloat(colors.count) }
}
}
The code that sets and renders the custom tiles:
/// Creates a MapOverlay with a custom tile URL, and adds it to the mapView
private func setupTileRenderer() {
let skin = AccountService.shared.userSettings.mapSettings.skin.rawValue
let poi = ""
let template = "\(ConfigKey.TILE_URL.value)\(skin)\(poi)_en_v2/{z}/{x}/{y}.png?v=3"
for overlay in mapView.overlays
{
if overlay is MapOverlay
{
mapView.removeOverlay(overlay)
}
}
let tileOverlay = MapOverlay(urlTemplate: template)
tileOverlay.canReplaceMapContent = true
tileOverlay.maximumZ = Int(maxZoomLevel)
mapView.insertOverlay(tileOverlay, at: 0)
}
import MapKit
import Foundation
class MapOverlay: MKTileOverlay {
override func url(forTilePath path: MKTileOverlayPath) -> URL {
var tileUrl = self.urlTemplate!.replacingOccurrences(of: "{z}", with: "\(path.z)")
tileUrl = tileUrl.replacingOccurrences(of: "{x}", with: "\(path.x)")
tileUrl = tileUrl.replacingOccurrences(of: "{y}", with: "\(path.y)")
return URL(string: tileUrl)!
}
}
The way I move the map during navigation:
func setCamera(center: CLLocationCoordinate2D, headingAngle: Double, animated: Bool = true) {
let camera = mapView.camera.copy() as! MKMapCamera
camera.heading = headingAngle
camera.pitch = CAMERA_PITCH
camera.centerCoordinate = center
camera.centerCoordinateDistance = currentCenterCoordinateDistance
mapView.setCamera(camera, animated: animated)
}
Upvotes: 0
Views: 253
Reputation: 6849
Probably MKGradientPolylineRenderer
already can do what you want.
If you still want to draw yourself: road width depends on the zoom level, see MKRoadWidthAtZoomScale https://developer.apple.com/documentation/mapkit/1452156-mkroadwidthatzoomscale?language=objc
and you might want do something like
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
let roadWidth = MKRoadWidthAtZoomScale(zoomScale)
let scaledLineWidth = lineWidth * roadWidth
context.setLineWidth(scaledLineWidth)
...
}
Upvotes: -1