I'm trying to add a circle overlay to my map. I followed the apple docs and got this far, but the overlay doesn't appear when I run the app. Here's my code...
#IBOutlet var map: MKMapView!
let location = CLLocationCoordinate2DMake(40.73085, -73.99750)
let regionRadius: CLLocationDistance = 5500
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
let diskRenderer: MKCircleRenderer = MKCircleRenderer.init()
diskRenderer.fillColor = UIColor.init(red: 0, green: 192, blue: 295, alpha: 1)
return diskRenderer
}
override func viewDidLoad() {
super.viewDidLoad()
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location, regionRadius, regionRadius)
map.setRegion(coordinateRegion, animated: true)
let diskOverlay: MKCircle = MKCircle.init(centerCoordinate: location, radius: 5000)
map.addOverlay(diskOverlay)
// Do any additional setup after loading the view.
}
I'm teaching myself all this stuff so forgive me if i've made an obvious mistake.
See the accepted answer to this question:
How to create MKCircle in Swift?
Steps are:
Ensure this controller implements MKMapViewDelegate:
class YourViewController: UIViewController, MKMapViewDelegate
In viewDidLoad set the delegate on map to self:
override func viewDidLoad() {
super.viewDidLoad()
map.delegate = self;`
Add the following function to render the overlay (change the color, etc):
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
if let overlay = overlay as? MKCircle {
let circleRenderer = MKCircleRenderer(circle: overlay)
circleRenderer.fillColor = UIColor.blueColor()
return circleRenderer
}
else {
return MKOverlayRenderer(overlay: overlay)
}
}
The radius of your circle is too big as it is currently set and the whole map will be blue. Set it to something small, like 1000, so you can see the circle.
objc in a class that is set as a mkmapviewdelegate:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolygon class]]) {
MKPolygon *polygon = (MKPolygon *)overlay;
MKPolygonRenderer *renderer = [[MKPolygonRenderer alloc] initWithPolygon:polygon];
renderer.strokeColor = [UIColor blueColor];
renderer.lineWidth = 5.;
return renderer;
}
return [[MKOverlayRenderer alloc] initWithOverlay:overlay];
}
Related
MKMapView class is added and assigned delegate:
let mapView: MKMapView = {
let map = MKMapView()
map.translatesAutoresizingMaskIntoConstraints = false
return map
}()
override func viewDidLoad() {
self.view.addSubview(mapView)
mapView.delegate = self
let center = mapView.centerCoordinate
NSLayoutConstraint.activate([
mapView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor, constant: 0),
mapView.leftAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leftAnchor, constant: 0),
mapView.rightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.rightAnchor, constant: 0),
mapView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, constant: 0)
])
}
Then the mapView is assigned center coordinate and span is set. zoomLevel is set to 1
let span = MKCoordinateSpan(latitudeDelta: 0, longitudeDelta: 360 / pow(2, Double(zoomLevel)) * Double(mapView.frame.size.width) / 256)
setRegion(MKCoordinateRegion(center: coordinate, span: span), animated: animated)
// I wrote URL like this because I was told that MKTileOverlay class will take care of z, x, and y value.
let urlTemplate = "https://tile.openweathermap.org/map/temp_new/{z}/{x}/{y}.png?appid={my API ID}"
// urlTemplate is added to MKTileOverlay
let overlay = MKTileOverlay(urlTemplate: urlTemplate)
overlay.canReplaceMapContent = true
// And just when I am about to add overlay to mapView, the app will crash:
self.mapView.addOverlay(overlay)
// It takes me to AppDelegate.swift and then it shows this message:
Thread 1: Exception: "Expected a MKTileOverlay but got (null)"
Everything was added to viewDidLoad().
I also added renderedFor delegate functions. Like this:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
var renderer = MKTileOverlayRenderer()
print("overlay rendered For: \(overlay)")
if overlay is MKTileOverlay {
renderer = MKTileOverlayRenderer(overlay:overlay)
renderer.alpha = 0.8
}
return renderer
}
I had the same problem and what was causing the crash for me was the initialisation of MKTileOverlayRenderer() without specifying an overlay.
So basically, your code inside the mapView(_:rendererFor:) delegate method should be:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
print("overlay rendered For: \(overlay)")
if let overlay = overlay as? MKTileOverlay {
renderer = MKTileOverlayRenderer(overlay: overlay)
renderer.alpha = 0.8
return renderer.alpha
} else {
return MKOverlayRenderer(overlay: overlay)
}
}
I created seven different polylines. However some of them are disappearing when I zoom in closely. Why it is happening? How can I prevent this?
Here is my polyline renderer:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(polyline: overlay as! MKPolyline)
renderer.strokeColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.9)
renderer.lineWidth = 2.2
return renderer
}
//Thousands of parameters sending as a parameter
func createPathWithPoints(_ points: [MKMapPoint]) {
let arc = MKPolyline(points: points, count: points.count)
mapView.addOverlay(arc)
}
Please help!
I had similar problem with using MKPolyline and below is what I did to fix this.
1) Make sure that you have your mapView delegate in viewDidLoad().
mapView.delegate = self
2) Add your overlay to the map.
mapView.addOverlay(polyLine())
I am using coredata in my project, so if myLocations are empty then I return empty MKPolyline()
private func polyLine() -> MKPolyline {
guard let locations = myLocations else {
return MKPolyline()
}
// Coordinates
let coords: [CLLocationCoordinate2D] = locations.map { location in
let location = location as! Location
return CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude)
}
return MKPolyline(coordinates: coords, count: coords.count)
}
3) We have access to rendererFor from MKMapViewDelegate. You can change color and width for polyline.
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
guard let polyline = overlay as? MKPolyline else {
return MKOverlayRenderer(overlay: overlay)
}
// Setup renderer
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = .systemBlue
renderer.lineWidth = 3
return renderer
}
I'm using Xcode 8.3.2 so first I import mapkit. Then I set markers to the map. Then I add the following code to add a polyline to the map but it won't show any.
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
self.mapView.delegate = self
super.viewDidLoad()
let template = "http://tile.openstreetmap.org/{z}/{x}/{y}.png"
let point1 = CLLocationCoordinate2D(latitude: 6.9271, longitude: 79.8612);
let point2 = CLLocationCoordinate2D(latitude: 9.6615, longitude: 80.0255);
let overlay = MKTileOverlay(urlTemplate: template)
overlay.canReplaceMapContent = true
let location = CLLocationCoordinate2DMake(6.878069, 79.892119)
mapView.add(overlay, level: .aboveLabels)
mapView.setRegion(MKCoordinateRegionMakeWithDistance(location, 1100, 1100), animated: true)
let pin = PinAnnotation(title: "Nimbus", subtitle: "Best", coordinate: location)
mapView.addAnnotation(pin)
let points: [CLLocationCoordinate2D]
points = [point1, point2]
let polyline = MKGeodesicPolyline(coordinates: points, count: 3)
mapView.add(polyline)
UIView.animate(withDuration: 1.5, animations: { () -> Void in
let span = MKCoordinateSpanMake(0.01, 0.01)
let region1 = MKCoordinateRegion(center: point1, span: span)
self.mapView.setRegion(region1, animated: true)
})
}
func mapView(_ mapview: MKMapView, rendererFor overlay: MKOverlay) ->MKOverlayRenderer{
if let overlayGeodesic = overlay as? MKGeodesicPolyline
{
let overLayRenderer = MKPolylineRenderer(polyline: overlayGeodesic)
overLayRenderer.lineWidth = 5
overLayRenderer.strokeColor = UIColor.blue
return overLayRenderer
}
return MKOverlayRenderer(overlay: overlay)
}
First you need to add this line, I think you already have added but anyway
self.mapView.delegate = self
After that you need to implement this MKMapViewDelegate method func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer and return the MKOverlayRenderer needed for your current overlay in this case MKPolylineRenderer this is an important part if you don't implement this method then you never will have your polyline rendered
implementation will be something like this
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let overlayGeodesic = overlay as? MKGeodesicPolyline
{
let overLayRenderer = MKPolylineRenderer(polyline: overlayGeodesic)
overLayRenderer.lineWidth = 5
overLayRenderer.strokeColor = UIColor.blue
return overLayRenderer
}
if let overlayTile = overlay as? MKTileOverlay{
let overLayRenderer = MKTileOverlayRenderer(tileOverlay: overlayTile)
return overLayRenderer
}
return MKOverlayRenderer(overlay: overlay)
}
And voila! there is your polyLine rendered
I have very simple View Controller to demonstrate this strange rendering behavior of MKPolyline. Nothing special just normal api calls.
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
map.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let p1 = CLLocationCoordinate2D(latitude: 51, longitude: 13)
var coords = [
p1,
CLLocationCoordinate2D(latitude: 51.1, longitude: 13),
CLLocationCoordinate2D(latitude: 51.2, longitude: 13),
CLLocationCoordinate2D(latitude: 51.3, longitude: 13)
]
let polyline = MKPolyline(coordinates: &coords, count: coords.count)
map.addOverlays([polyline], level: .aboveRoads)
let cam = MKMapCamera(lookingAtCenter: p1, fromDistance: 1000, pitch: 45, heading: 0)
map.setCamera(cam, animated: true)
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let r = MKPolylineRenderer(overlay: overlay)
r.strokeColor = UIColor.blue
return r
}
}
The rendering of the polyline is very strange. During zooming and panning You can see some artifacts.
Take a look at pictures below:
Initial Screen
After some panning
After zooming out and zooming in again
How to fix this? I was trying to implement my own renderer but its the same situation. Like overaly is cached and it's not redrawing on time. I'm working on iOS 10, iPhone 6, Simulator from iOS SDK 10 xCode 8.
Swift 3 solution :
Create a subclass of MKPolylineRenderer
class CustomPolyline: MKPolylineRenderer {
override func applyStrokeProperties(to context: CGContext, atZoomScale zoomScale: MKZoomScale) {
super.applyStrokeProperties(to: context, atZoomScale: zoomScale)
UIGraphicsPushContext(context)
if let ctx = UIGraphicsGetCurrentContext() {
ctx.setLineWidth(self.lineWidth)
}
}
}
Then use it in your rendererFor MapKit delegate :
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = CustomPolyline(overlay: overlay)
renderer.strokeColor = UIColor.red
renderer.lineWidth = 100
return renderer
}
Your polylines won't re-render after zooming thus avoiding the artifacts
Been trying to figure this out for hours. Trying to do something simple: draw a circle overlay on mapview. BLEvent conforms to the MKMapOverLay protocol and has the information needed (radius, coordinates) Yes my view controller has been set as the map view delegate.
In my viewDidLoad..
for (BLEvent *event in self.model.events) {
[self.mapView addOverlay:event];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay{
if ([overlay isKindOfClass:[BLEvent class]]){
BLEvent *event = (BLEvent *)overlay;
MKCircle *circle = [MKCircle circleWithCenterCoordinate:event.coordinate radius:event.radius];
MKCircleRenderer *circleR = [[MKCircleRenderer alloc] initWithCircle:circle];
circleR.fillColor = [UIColor blueColor];
return circleR;
} else{
return nil;
}
}
I checked with this, and the renderer was in fact called> I know the coordinates are valid because I had annotations that showed in the map.
- (void)mapView:(MKMapView *)mapView didAddOverlayRenderers:(NSArray *)renderers{
NSLog(#"%#", ((MKCircleRenderer *)[renderers objectAtIndex:0]).fillColor);
}
The circle is not drawn because the MKCircle you create is not added as overlay to the map.
In order to make this work your BLEvent class needs to be a MKCircle subclass.
Then try this:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id < MKOverlay >)overlay{
if ([overlay isKindOfClass:[BLEvent class]]){
BLEvent *event = (BLEvent *)overlay;
MKCircleRenderer *circleR = [[MKCircleRenderer alloc] initWithCircle:event];
circleR.fillColor = [UIColor blueColor];
return circleR;
}
return nil;
}
A working version for iOS 8.0 and SWIFT
import Foundation
import MapKit
class MapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate{
var locationManager: CLLocationManager = CLLocationManager()
#IBOutlet var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// We use a predefined location
var location = CLLocation(latitude: 46.7667 as CLLocationDegrees, longitude: 23.58 as CLLocationDegrees)
addRadiusCircle(location)
}
func addRadiusCircle(location: CLLocation){
self.mapView.delegate = self
var circle = MKCircle(centerCoordinate: location.coordinate, radius: 10000 as CLLocationDistance)
self.mapView.addOverlay(circle)
}
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKCircle {
var circle = MKCircleRenderer(overlay: overlay)
circle.strokeColor = UIColor.redColor()
circle.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.1)
circle.lineWidth = 1
return circle
} else {
return nil
}
}
}