Swift MapKit - create a polygon - swift

I am trying to create a polygon on my map to show a boundry. But when I run my app nothing appears. I can see the map but not the polygon What's my error? thanks.
import UIKit
import MapKit
import CoreLocation
class MapScreen : UIViewController {
#IBOutlet var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
mapView.delegate = self as! MKMapViewDelegate
// center the location on the screen
var location = CLLocationCoordinate2DMake(41.308792, -72.928641)
var span = MKCoordinateSpanMake(0.01, 0.01)
var region = MKCoordinateRegionMake(location, span)
mapView.setRegion(region, animated: true)
//calling the method
addBoundry()
}
func addBoundry(){ //creation of a polygon
var points = [CLLocationCoordinate2DMake(41.311674, -72.925506),
CLLocationCoordinate2DMake(41.307308, -72.928694),
CLLocationCoordinate2DMake(41.307108, -72.928324),
CLLocationCoordinate2DMake(41.307892, -72.930285),
CLLocationCoordinate2DMake(41.307892, -72.931223),
CLLocationCoordinate2DMake(41.307227, -72.932494),
CLLocationCoordinate2DMake(41.308452, -72.931663),
CLLocationCoordinate2DMake(41.308730, -72.932773),
CLLocationCoordinate2DMake(41.308496, -72.931614),
CLLocationCoordinate2DMake(41.308496, -72.931614),
CLLocationCoordinate2DMake(41.311288, -72.931630),
CLLocationCoordinate2DMake(41.311659, -72.930945),
CLLocationCoordinate2DMake(41.312893, -72.932153),
CLLocationCoordinate2DMake(41.313433, -72.930542),
CLLocationCoordinate2DMake(41.313324, -72.929963),
CLLocationCoordinate2DMake(41.312758, -72.929027),
CLLocationCoordinate2DMake(41.312373, -72.927167),
CLLocationCoordinate2DMake(41.311674, -72.925506)]
let polygon = MKPolygon(coordinates: &points, count: points.count)
mapView.add(polygon)
}
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKPolygon {
let polygonView = MKPolygonRenderer(overlay: overlay)
polygonView.fillColor = UIColor(red: 0, green: 0.847, blue: 1, alpha: 0.25)
return polygonView
}
return nil
}
}

Your signature for mapView(_:rendererFor:) is incorrect. In Swift 3, it would be:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
...
}
If you formally declare your conformance to protocols like MKMapViewDelegate, it will often warn you about these things. For example, best practice would be do so in a class extension rather than in the class definition itself:
extension MapScreen: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
...
}
}
By the way, if you do that, not only do you get warnings about protocol conformance, but you also don't need to cast it when setting the delegate:
mapView.delegate = self

Related

Error when adding polygon overlay to MKMapView

I have a map view that is conforming to UIViewRepresentable to use it in my SwiftUI app. When my view model updates with data after a successful API call, I add to my array of overlays with an array of coordinates to represent a map boundary. This array of overlays is then passed to the view and then the overlay is added as an MKPolygon in updateUIView. I return a render this polygon in the delegate method.
struct MapViewUIKit: UIViewRepresentable {
#Binding var annotations: [MKPointAnnotation]
#Binding var mapRect: MKMapRect
#Binding var overlays: [MKOverlay]
let mapView = MKMapView()
func makeUIView(context: Context) -> MKMapView {
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
view.setVisibleMapRect(mapRect, animated: true)
if annotations.count != view.annotations.count {
view.removeAnnotations(view.annotations)
view.addAnnotations(annotations)
}
if overlays.count != view.overlays.count {
view.removeOverlays(view.overlays)
view.addOverlays(overlays)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapViewUIKit
init(_ parent: MapViewUIKit) {
self.parent = parent
}
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
parent.mapRect = mapView.visibleMapRect
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolygon {
let renderer = MKPolygonRenderer(overlay: overlay)
renderer.strokeColor = .magenta
renderer.fillColor = .blue
return renderer
}
return MKOverlayRenderer()
}
}
}
However, when I zoom into the area where the polygon should be rendered, I get the following error and there is no overlay
Wrapped around the polygon without finishing... :-(
List has 6 nodes:
1 2 4 5 6 0
2022-03-14 15:01:36.159966-0400 Synop[3702:59086] [VKDefault] Building failed to triangulate!
I've tried searching what this error means to no avail. I know my overlay is being added and with the proper coordinates as well with print statements in the relevant places.

Draw polygon on MapView using Swift

I have a MKMapView, and a set of coordinates I want to connect with a polygon to draw a rectangle. Below is my code that I have started with.
import UIKit
import MapKit
class mapViewViewController: UIViewController {
#IBOutlet var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
map?.delegate=self
let startingPoint1 = ...
let startingPoint2 = ...
let endingPoint1 = ...
let endingPoint2 = ...
var coordinateInput:[CLLocationCoordinate2D]=[startingPoint1,startingPoint2,endingPoint1,endingPoint2]
let line = MKPolygon(coordinates:&coordinateInput, count:4)
map.addOverlay(line)
}
extension mapViewViewController: MKMapViewDelegate{
func map(_ map: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer{
if overlay is MKPolygon{
let renderer = MKPolygonRenderer(polygon: overlay as! MKPolygon)
renderer.fillColor=UIColor.red.withAlphaComponent(0.5)
renderer.strokeColor=UIColor.orange
renderer.lineWidth=4
return renderer
}
return MKOverlayRenderer()
}
}
The issue I'm running into is that when I launch the map view nothing is displayed on top of the map as expected. I have confirmed all of my coordinates are valid and what I intended them to be, but my current setup is not drawing on the map as I expect. What is the correct way to accomplish this?
Your signature for rendererFor is incorrect. Add a breakpoint or log statement in your method and you will see it is not getting called.
The correct signature is:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { ... }

(SwiftUI) MKOverlays (parsed from GeoJSON) are added, but do not show on screen, am I missing something?

I am attempting to show a UIKit/MapKit view in SwiftUI, following code very similar to this project on Github but implementing the GeoJSON render/overlay code from the Apple Guide for Optimizing Map Views with Filtering and Camera Constraints.
I have an 'EventDataSource' class which (successfully) parses the GeoJSON objects from a local file, in a UIViewRepresentable class, I then load this data, and add the overlays in the updateUIView function.
Placing a breakpoint in the mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) function shows that each polygon has the correct value for _pointCount, and I have added a print() line for the coordinates, which successfully prints out the coordinates of all the objects whenever the view is loaded.
Xcode shows no errors and builds fine, when run, the MapKit view loads, yet no overlays appear on screen.
The exact same mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) function, used in a UIKit ViewController renders the objects perfectly. In SwiftUI, I get successful parsing, yet only an empty map. Is there something I am missing?
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var dataSource: EventDataSource = EventDataSource()
func makeUIView(context: Context) -> MKMapView {
let map = MKMapView()
map.delegate = context.coordinator
return map
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.region = .event
mapView.addOverlays(dataSource.overlays)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, MKMapViewDelegate {
var control: MapView
init(_ control: MapView) {
self.control = control
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polygon = overlay as? MKPolygon {
let renderer = MKPolygonRenderer(polygon: polygon)
renderer.fillColor = UIColor(named: "OverlayFill")
renderer.strokeColor = UIColor(named: "OverlayStroke")
renderer.lineWidth = 1.0
print("\(polygon.coordinate.longitude), \(polygon.coordinate.latitude)") // succesfully prints the coordinate of each object
return renderer
}
else if let multiPolygon = overlay as? MKMultiPolygon {
let renderer = MKMultiPolygonRenderer(multiPolygon: multiPolygon)
renderer.fillColor = UIColor(named: "OverlayFill")
renderer.strokeColor = UIColor(named: "OverlayStroke")
renderer.lineWidth = 1.0
print("\(multiPolygon.coordinate.longitude), \(multiPolygon.coordinate.latitude)") // succesfully prints the coordinate of each object
return renderer
}
return MKOverlayRenderer(overlay: overlay)
}
}
}
I have tried commenting out the line
mapView.addOverlays(dataSource.overlays)
When this line is not there, the coordinates do not print to the console, so I am sure that the function is being called.
Any help will be greatly appreciated, thanks!
if you try this
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var coordinate: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let span = MKCoordinateSpan(latitudeDelta: 2.0, longitudeDelta: 2.0)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
}
}
then in your view add
MapView(coordinate: "the coordinates")
check out the
https://developer.apple.com/tutorials/swiftui/creating-and-combining-views

How do I add image overlays to an MKMapView?

----------UPDATED------------
original question at the bottom
I've gotten pretty far, and I have this now:
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var mapView: MKMapView!
var locationManager: CLLocationManager!
var mapOverlay: MKOverlay!
override func viewDidLoad() {
super.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 = "zurich"
mapView.addOverlay(tile)
//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.
}
// mapView delegate function
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolygonRenderer(overlay: overlay)
renderer.fillColor = UIColor.red
return renderer
}
}
I now need to know how to replace the renderer.fillColor = UIColor.red with something that will display my image.
Thanks once again
----- original question ------
So, I'm new to Swift and MapKit and I want to add a simple image overlay on top of an MKMapView. I've found a few answers, but they're all confusing, and they are all for Swift 3 and earlier.
I've found that a delegate for the map view is needed, is that a file?
I have already created a map view using the main view controller.
This is what I've done so far (this is in the ViewController.swift file):
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate
{
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib
let location = CLLocationCoordinate2D(latitude: 47.457925,
longitude: 8.548466)
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let region = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
}
}
Thank you and I hope you can help!
There are a lot of way to embed image into your maps.
Annotation Views
Callouts
Custom Map Tile
Explain your need more, and maybe we can help better to how to get there.
You are adding overlay over the map. We want to change with specific map tile.
func createLocalUrl(forImageNamed name: String) -> URL? {
let fileManager = FileManager.default
let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let url = cacheDirectory.appendingPathComponent("\(name).png")
guard fileManager.fileExists(atPath: url.path) else {
guard
let image = UIImage(named: name),
let data = image.pngData()
else { return nil }
fileManager.createFile(atPath: url.path, contents: data, attributes: nil)
return url
}
return url
}
func setupTiles() {
let url = createLocalUrl(forImageNamed: "yourImageName")
let template = url?.absoluteString
let overlay = MKTileOverlay(urlTemplate: template)
overlay.canReplaceMapContent = true
self.tileOverlay = overlay
mapView.addOverlay(overlay)
self.tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
}
func isInDesiredArea(middlePoint: MKMapPoint) -> Bool {
//mapView has convert function which converts CGPoint ->
//CLLocationCoordinate2D and vice versa Use this function and,
//Your polygon has boundingMapRect which has contains function.
//Also your map has func mapView(_ mapView: MKMapView,
//regionDidChangeAnimated animated: Bool) which runs whenever region changes..
return myBoundsPolygon.boundingMapRect.hasContain(middlePoint)
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
//Convert middle point of your view to CLLocationCoordinate2D
//Convert your coordinate to MKMapPoint
if isInDesiredArea(middlePoint: point) {
setupTiles()
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
....
if overlay is MKTileOverlay {
return tileRenderer
}

Make custom location annotations in using Mapbox

I am using Mapbox to create my app. When I click a button I would like it to mark my current location and add a marker to it as it currently does. I then would like the abiilty to tap on this marker and have it display the current location information such as the adress of the marked point.
Right now all I have is...
https://imgur.com/a/RSx0G
I would like to note that I am using Xcode 9.1 and Swift 4. Thank you for all your feedback in advance.
Currently the swift file looks like...
import Foundation
import UIKit
import CoreLocation
import Mapbox
import MapKit
import MapboxGeocoder
class SecondViewController: UIViewController, CLLocationManagerDelegate, MGLMapViewDelegate, UITextFieldDelegate {
let geocoder = Geocoder.shared
let dismissesAutomatically: Bool = false
let isAnchoredToAnnotation: Bool = true
weak var delegate: MGLCalloutViewDelegate?
let tipHeight: CGFloat = 10.0
let tipWidth: CGFloat = 20.0
#IBOutlet var mapView: MGLMapView!
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func markStuff(_ sender: Any) {
}
#IBAction func refLocation(_ sender: Any) {
manager.startUpdatingLocation()
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
return nil
}
func mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation)
{
print("tap on callout")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
mapView.setCenter(center, zoomLevel: 10, animated: true)
let annotation = MGLPointAnnotation()
annotation.coordinate = location.coordinate
mapView.selectAnnotation(annotation, animated: true)
annotation.title = "Testing"
annotation.subtitle = "\(annotation.coordinate.latitude), \(annotation.coordinate.longitude)"
self.mapView.addAnnotation(annotation)
manager.stopUpdatingLocation()
You can create a subclass of MGLUserLocationAnnotationView, then use that as your view for the MGLUserLocation annotation.
For example, if your custom subclass was called YourLocationAnnotationView(), you could do something like:
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {This custom view is created below.
if annotation is MGLUserLocation && mapView.userLocation != nil {
return YourLocationAnnotationView()
}
return nil
}
}
For a complete implementation, see this example from the official documentation.