How to draw a polygon overlay using MapKit and swift - ios

I am trying to get the following code to draw a polygon on the map but for some reason it doesn't work. What did i go wrong here?
import UIKit
import MapKit
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
let initialLocation = CLLocation(latitude: 49.140838, longitude: -123.127886)
centerMapOnLocation(initialLocation)
addBoundry()
}
func addBoundry()
{
var points=[CLLocationCoordinate2DMake(49.142677, -123.135139),CLLocationCoordinate2DMake(49.142730, -123.125794),CLLocationCoordinate2DMake(49.140874, -123.125805),CLLocationCoordinate2DMake(49.140885, -123.135214)]
let polygon = MKPolygon(coordinates: &points, count: points.count)
mapView.addOverlay(polygon)
}
let regionRadius: CLLocationDistance = 1000
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
}
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKPolygon {
let polygonView = MKPolygonRenderer(overlay: overlay)
polygonView.strokeColor = UIColor.magentaColor()
return polygonView
}
return nil
}

It would appear that you've implemented mapView(_:renderFor:) (previously known as rendererForOverlay) as a global function. The method must be within the ViewController class definition or, even better, to keep our code nicely organized, within a MKMapViewDelegate extension:
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
...
}
}
Also, make sure that you've defined the view controller to be the delegate of the map view. It's probably easiest to do that in IB, but you can also do it in viewDidLoad:
mapView.delegate = self

Related

Polyline not showing in swiftUI

Hi everyone I've been working for a few days to show a straight line on my map. I use swiftUI and mapkit to render the map. what I want to achieve is a straight line between the two annotations, these are shown on the map.
Dit is de code die ik op dit moment heb. Ik hoop dut jullie mij kunnen helpen want ik kom er niet uit.
import MapKit
struct MapViewWorldDetail: UIViewRepresentable {
var StartCoordinate: CLLocationCoordinate2D
var EndCoordinate: CLLocationCoordinate2D
var name: String
#Binding var region: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let span = MKCoordinateSpan(latitudeDelta: 50, longitudeDelta: 50)
let region = MKCoordinateRegion(center: self.region, span: span)
// view.mapType = MKMapType.satelliteFlyover;
view.setRegion(region, animated: true)
let annotation = MKPointAnnotation()
annotation.coordinate = StartCoordinate
annotation.title = name
view.addAnnotation(annotation)
let annotationEnd = MKPointAnnotation()
annotationEnd.coordinate = EndCoordinate
annotationEnd.title = name
view.addAnnotation(annotationEnd)
let aPolyline = MKGeodesicPolyline(coordinates: [StartCoordinate, EndCoordinate], count: 2)
view.addOverlay(aPolyline)
}
}
Note on the straight line you are drawing: The line that you are drawing using MKGeodesicPolyline has this note in the Apple Developer Documentation:
MKGeodesicPolyline - When displayed on a two-dimensional map view, the line segment between any two points may appear curved.
Example of working code
In SwiftUI, you'll need to implement the MKMapViewDelegate in a Coordinator class, which is where the Overlay handling is taken care of:
Add this to func updateUIView
func updateUIView(_ view: MKMapView, context: UIViewRepresentableContext<MapView>) {
// Stuff you already had in updateUIView
// :
// adding this at the end is sufficient
mapView.delegate = context.coordinator
}
Add this to your struct, struct MapViewWorldDetail
// MARK: - Coordinator for using UIKit inside SwiftUI.
func makeCoordinator() -> MapView.Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, MKMapViewDelegate {
var control: MapView
init(_ control: MapView) {
self.control = control
}
// MARK: - Managing the Display of Overlays
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
print("mapView(_:rendererFor:)")
if let polyline = overlay as? MKPolyline {
let polylineRenderer = MKPolylineRenderer(overlay: polyline)
polylineRenderer.strokeColor = .red
polylineRenderer.lineWidth = 3
return polylineRenderer
}
return MKOverlayRenderer(overlay: overlay)
}
}
Good references to review:
Sample project that implements the Coordinator class & delegates (but does not do overlays)
https://github.com/roblabs/ios-map-ui/tree/master/MapKit-SwiftUI-for-iOS-macOS
Medium posts by others
https://medium.com/better-programming/exploring-mapkit-on-ios-13-1a7a1439e3b6
https://medium.com/flawless-app-stories/mapkit-in-swiftui-c0cc2b07c28a

MKMapview delegate never called even with set delegate

I have a UIView that integrate a scrollview. Inside the scrollview I have a map where a user need to move a pin. I'm gonna reuse the location of pin later.
I'm setting strong reference and setting delegate ( both design and code) for the map. I'm able to see the pin created but to have custom icon and possibility to set it draggable, I have implement the mapView( viewFor MKAnnotation
This seems to be never called and I'm not getting why.
I also try to get another delegate as didfinishloading to understand if problem is related to the mkannotation but also this one is never been called.
import Foundation
import Firebase
import MapKit
import UIKit
public class Setup: UIViewController,CLLocationManagerDelegate,MKMapViewDelegate{
var ChargePointID = ""
#IBOutlet weak var chargepointIDLabel: UILabel!
let locationManager = CLLocationManager()
var pin: customPin!
//setting up variables
//pricing textfield and slider
#IBOutlet weak var priceLabel: UITextField!
#IBOutlet weak var priceSlider: UISlider!
#IBOutlet var map: MKMapView!
override public func viewDidLoad() {
super.viewDidLoad()
chargepointIDLabel.text = ChargePointID
self.map.delegate = self
self.map.mapType = .hybrid
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
//create liocation relative to user
let location = CLLocationCoordinate2D(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
//centering map to user location
let region = MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
self.map.setRegion(region, animated: true)
//creating a pin of type custompin
pin = customPin(pinTitle: ChargePointID, pinSubTitle: "", location: location)
map.addAnnotation(pin)
}
private func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
print("locations = \(locValue.latitude) \(locValue.longitude)")
}
private func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
print("annotation title == \(String(describing: view.annotation?.title!))")
}
private func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
print("ok")
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "customannotation")
annotationView.image = UIImage(named:"setuppin")
annotationView.canShowCallout = true
return annotationView
}
func mapViewDidFinishLoadingMap(_ mapView: MKMapView) {
print(" map has finished")
}
private func map(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
print("ok")
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "customannotation")
annotationView.image = UIImage(named:"setuppin")
annotationView.canShowCallout = true
return annotationView
}
}
class customPin: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
init(pinTitle:String, pinSubTitle:String, location:CLLocationCoordinate2D) {
self.title = pinTitle
self.subtitle = pinSubTitle
self.coordinate = location
}
}
private is not necessary. Replace
private func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
with
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
this was cause Xcode was raising this warning
instance method 'mapView(:viewFor:)' nearly matches optional requirement 'mapView(:viewFor:)' of protocol 'MKMapViewDelegate'
and suggest make it private as fix. btw even without private not raise anything
DAMN!!!!!
I solved it.... the warning was cause my class was declared ad public and delegate need to be too.
I removed the public statement from my class and everything start to work .
so problem in end was main class declared as public !

How do I allow the user to resize an mkcircle object in a map view using a gesture recognizer on the mkcircle?

In Appleā€™s iOS Reminders app, you can set a location area at which you would be reminded of your reminder. When you set the location area, you are allowed to draw a circle that covers the area of the location you want to be reminded at. How would I duplicate this feature. I particularly would like to know how to allow the user to resize the circle on a map view by dragging his finger on the edge of the circle. I have already figured out how to create the circle.
Here is my code so far:
import UIKit
import MapKit
class ViewController: UIViewController {
// MARK: - Outlets
#IBOutlet weak var mapView: MKMapView!
// MARK: - Variables and Constants
let regionRadius: CLLocationDistance = 1000
let circularRegionRadius: CLLocationDistance = 500
let locationManager = CLLocationManager()
// MARK: - View
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
centerMapOnLocation(location: locationManager.location!)
}
// MARK: - Actions
#IBAction func addRegion(_ sender: UILongPressGestureRecognizer) {
print("addRegion(_:)")
let longPress = sender
let touchLocation = longPress.location(in: mapView)
let coordinates = mapView.convert(touchLocation, toCoordinateFrom: mapView)
let region = CLCircularRegion(center: coordinates, radius: circularRegionRadius, identifier: "geofence")
mapView.removeOverlays(mapView.overlays)
locationManager.startMonitoring(for: region)
let circle = MKCircle(center: coordinates, radius: region.radius)
mapView.add(circle)
}
// MARK: - Helper Functions
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
regionRadius, regionRadius)
mapView.setRegion(coordinateRegion, animated: true)
}
// MARK: - Memory Warning
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
print("mapView(_:rendererFor:_:)")
guard let circelOverLay = overlay as? MKCircle else {return MKOverlayRenderer()}
let circleRenderer = MKCircleRenderer(circle: circelOverLay)
circleRenderer.strokeColor = .blue
circleRenderer.fillColor = .blue
circleRenderer.alpha = 0.2
return circleRenderer
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("locationManager(_:didUpdateLocations:)")
locationManager.stopUpdatingLocation()
mapView.showsUserLocation = true
}
}

Swift: Subclass MKPolyline

I'm developing an app that request multiple directions (MKDirectionsRequest) and draw the routes in a mapView, all ok.
But I'm facing a problem: I want to draw each route with different color.
The first idea was simple: use title/subtitle to 'tag' different MKPolyline so I can set the color I want in the delegate function:
mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer
but I don't like this solution because it's 'ugly' and I will have to parse a string the day I will have to pass different params (traffic ..)
The second simple solution was to subclass MKPolyline, yeh simple ..
as MKPolyline doesn't have a designated initializer, this is impossible (is it ?)
[edit]: I want to create a subclass of MKPolyline to copy "on it" the already created MKPolyline returned by MKDirectionsRequest.routes but I can't figure out how to override the read-only params (Apple says that we should override them in the subclass and add the setter, but I have an infinite loop in the setter witch is .. normal)
if was using objC, it will be simple to "inject" code at runtime and add my params, but I'm using swift.
could anyone help on this, thanks.
Simpler method not requiring a custom renderer :
import UIKit
import MapKit
class CustomPolyline : MKPolyline {
var color: UIColor?
}
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// setup mapView
mapView.delegate = self
// set map view region
let location : CLLocationCoordinate2D = CLLocationCoordinate2DMake(51.4987, 0.007);
let viewRegion = MKCoordinateRegionMakeWithDistance(location, 400, 400)
mapView.setRegion(viewRegion, animated:true )
// add red line
let coords1 = [CLLocationCoordinate2D(latitude: 51.499526, longitude: 0.004785),CLLocationCoordinate2D(latitude: 51.500007, longitude: 0.005493)]
let polyline1 = CustomPolyline(coordinates: coords1, count: coords1.count)
polyline1.color = UIColor.red
mapView.add(polyline1)
// add blue line
let coords2 = [CLLocationCoordinate2D(latitude: 51.498103, longitude: 0.007574), CLLocationCoordinate2D(latitude: 51.498190, longitude: 0.009677)]
let polyline2 = CustomPolyline(coordinates: coords2, count: coords2.count)
polyline2.color = UIColor.blue
mapView.add(polyline2)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is CustomPolyline {
let polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = (overlay as! CustomPolyline).color
polylineRenderer.lineWidth = 4
return polylineRenderer
}
return MKOverlayRenderer()
}
}
Why do you want to set color to your custom MKOverlay, couldnt you simply set the desired color to MKOverlayRenderer ? It should be quite simpler then.
You could also easily subclass MKPolyline. I dont see why you were not able to do that.
If you want to be able to create a custom MKOverlay and set custom properties, then, draw it into the map, you would also need to create your own renderer. That seems like a big job. But, here is an example how you could do that.
import UIKit
import MapKit
public class CustomOverlay: MKPolyline {
public var customColor: UIColor?
}
public class CustomRenderer: MKPolylineRenderer {
override public func strokePath(_ path: CGPath, in context: CGContext) {
guard let overlayColor = self.overlay as? CustomOverlay, let color = overlayColor.customColor else {
super.strokePath(path, in: context)
return
}
context.saveGState()
context.setStrokeColor(color.cgColor)
context.addPath(path)
context.drawPath(using: .stroke)
context.restoreGState()
}
}
public extension CLLocationCoordinate2D {
static let Salo = CLLocationCoordinate2DMake(60.3909, 23.1355)
static let Turku = CLLocationCoordinate2DMake(60.454510, 22.264824)
static let Helsinki = CLLocationCoordinate2DMake(60.170833, 24.9375)
}
public class ViewController: UIViewController, MKMapViewDelegate {
private var mapView: MKMapView!
override public func viewDidLoad() {
super.viewDidLoad()
createMapView()
setupMapView()
}
private func createMapView() {
mapView = MKMapView(frame: .zero)
mapView.translatesAutoresizingMaskIntoConstraints = false
mapView.delegate = self
view.addSubview(mapView)
[mapView.topAnchor.constraint(equalTo: view.topAnchor),
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor)].forEach { $0.isActive = true }
}
private func setupMapView() {
let coordinates: [CLLocationCoordinate2D] = [.Helsinki, .Turku]
let customPolyLine = CustomOverlay(coordinates: coordinates, count: coordinates.count)
customPolyLine.customColor = UIColor.red
mapView.add(customPolyLine)
let coordinateSpan = MKCoordinateSpan(latitudeDelta: 3, longitudeDelta: 3)
let region = MKCoordinateRegion(center: .Salo, span: coordinateSpan)
mapView.setRegion(region, animated: true)
}
// MARK: MKMapViewDelegate
public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
/* return a simple renderer */
// let renderer = MKPolylineRenderer(overlay:overlay)
// renderer.lineWidth = 2
// renderer.lineDashPattern = [1, 2, 1]
// renderer.strokeColor = UIColor.red
// return renderer
/* a custom renderer */
let customRenderer = CustomRenderer(overlay: overlay)
customRenderer.lineWidth = 2
customRenderer.strokeColor = UIColor.green // this color is not used, since we apply color from overlay inside strokePath(:inContext:) method for custom renderer
customRenderer.lineDashPattern = [1, 2, 1]
return customRenderer
}
}

How do I create an image overlay and add to MKMapView in Swift 2?

I am trying to learn MapKit and am now trying to add an image (not a polygon, circle, rectangle, etc.) as an overlay to the map view. I can't seem to find any sample code to help explain how to do this.
So far my code in ViewController.swift is:
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var mapView: MKMapView!
var locationManager: CLLocationManager!
var mapOverlay: MKOverlay!
override func viewDidLoad() {
super.viewDidLoad()
//Setup our Location Manager
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
//Setup our Map View
mapView.delegate = self
mapView.mapType = MKMapType.Satellite
mapView.showsUserLocation = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I know I need to use:
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer
and the drawMapRect function but I don't know what to put in them. Also, it would be useful if all the code is in ViewController.swift
I only need one overlay so I don't need a class. I need it to be an image overlay (so it can resize), not an annotation. I also tried to follow the tutorial at http://www.raywenderlich.com/30001/overlay-images-and-overlay-views-with-mapkit-tutorial but there is not a section with all the code and I find it hard to follow along. Can you people please help me how to create image MKOverlays and add them to MKMapKit?
Thanks in advance...
Swift 3
The following works for me in Swift 3.
class ImageOverlay : NSObject, MKOverlay {
let image:UIImage
let boundingMapRect: MKMapRect
let coordinate:CLLocationCoordinate2D
init(image: UIImage, rect: MKMapRect) {
self.image = image
self.boundingMapRect = rect
self.coordinate = rect.centerCoordinate
}
}
class ImageOverlayRenderer : MKOverlayRenderer {
override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
guard let overlay = self.overlay as? ImageOverlay else {
return
}
let rect = self.rect(for: overlay.boundingMapRect)
UIGraphicsPushContext(context)
overlay.image.draw(in: rect)
UIGraphicsPopContext()
}
}
Then as usual, in your MapView delegate:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is ImageOverlay {
return ImageOverlayRenderer(overlay: overlay)
}
...
}
And whenever you need to add an image to your map:
let overlay = ImageOverlay(image: myImage, rect: desiredLocationAsMapRect)
mapView.add(overlay)
Note, I always ensure that the rect has the same aspect ratio as the image. Otherwise, I suspect image distortion will result.
Apple do have some sample code for displaying overlays but it is in Objective-C so you will have to transpose it to Swift.
Essentially there are two things you need to do (and both can be done in your view controller). The first is to create the overlay and add it to the map i.e. somewhere in viewDidLoad():
var points = [CLLocationCoordinate2D(latitude: -29.8122, longitude: 148.6351),
CLLocationCoordinate2D(latitude: -27.9307, longitude: 148.6351),
CLLocationCoordinate2D(latitude: -27.9307, longitude: 150.9909),
CLLocationCoordinate2D(latitude: -29.8122, longitude: 150.9909)]
let tile = MKPolygon(coordinates: &points, count: points.count)
tile.title = "Moree"
mapView.addOverlay(tile)
Note that points has to be a var as you are dereferencing it for the tile creation.
The second step is then to define the renderer:
// mapView delegate function
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
if overlay.isKindOfClass(MKPolygon) {
let renderer = MKPolygonRenderer(overlay: overlay)
renderer.fillColor = UIColor.cyanColor().colorWithAlphaComponent(0.2)
renderer.strokeColor = UIColor.blueColor().colorWithAlphaComponent(0.7)
renderer.lineWidth = 3
return renderer
}
fatalError()
}
Using Swift 2.1/Xcode 7.2, this renders a tile for me in a location in Australia. So you may want to tweak the lat/lons to give you an overlay in your map - map defaults are to your continent.

Resources