SwiftUI MapKit - Multiple annotations in same map - swift

I've had a look at similar questions but nothing seems to answer this question exactly.
I have a Map view and I want to add pin overlays, as well as a poly line route overlay on top of it. I am very very new at SwiftUI dev (this is my first app), so some help would be appreciated.
At the moment the map only renders the pins, and not the overlay. I'm assuming they need to be put into the same method but cannot figure out how you can return multiple overlays to the mapView.
Here is the Coordinator code:
class Coordinator: NSObject,MKMapViewDelegate{
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
//draw pins
if annotation.isKind(of: MKUserLocation.self){return nil}
else{
let pinAnnotation = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "PIN_VIEW")
pinAnnotation.tintColor = .red
pinAnnotation.animatesDrop = true
pinAnnotation.canShowCallout = true
return pinAnnotation
}
}
private func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKPolylineRenderer? {
//draw route
let render = MKPolylineRenderer(overlay: overlay)
render.strokeColor = .orange
render.lineWidth = 2
return render;
}
}
Here is the output: (no route is drawn). I tried to put the annotations within the same method but couldn't figure out how to return both at the same time (pins are of type MKAnnotationView and the route is of type MKPolylineRenderer...
Help would be really appreciated. Thanks

It should be a separate method, but it shouldn’t be private. Also, the return type should be MKOverlayRenderer.

Related

Set limit on Clustering annotations using Mapkit

I have added clustering with annotations in my project and it's working fine but having problem to set the limit.
Please check below image here, grouping is done for 6 annotations but I want to set clustering limit which should start grouping at 20 (So never see one of these with the number 19 or lower)
func mapView(_ mapView: MKMapView, clusterAnnotationForMemberAnnotations memberAnnotations: [MKAnnotation]) -> MKClusterAnnotation {
return MKClusterAnnotation(memberAnnotations: memberAnnotations)
}
Also I tried this but not working for me.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let groupedpin = annotation as! MKClusterAnnotation
if groupedpin.memberAnnotations.count >= 20 { //<— Not working
let view = PlaceClusterAnnotationView.annotationView()
view._count = groupedpin.memberAnnotations.count.description
return view
}
return MKAnnotationView()
}
You cannot force annotations to cluster, but you can prevent annotations from clustering. Do not give any view a clusteringIdentifier (meaning, set it to nil to prevent clustering; an empty string "" still acts as a clustering identifier) until you want it to be able to cluster, and keep the view's displayPriority at required until you want it to be able to cluster. To try to make a view cluster, set its displayPriority to 0. If you suddenly set twenty views' displayPriority to 0 then if they are close enough together they will probably all cluster. But you can't guarantee this.

Calling mapView(_:viewFor:) when user location changes

I want to have the user location being shown as a custom image (not the blue beacon). I also want that whenever the user location changes its coordinates, this custom image is rotated accordingly to represent the user course. In order to achieve that, I implemented the code below. It is being called with annotation type userLocation when the app is started, so that the user location custom image is being shown fine. The problem is that when I simulate a change in user location coordinates, the delegate mapView(_:viewFor:) method is never called again with an annotation type userLocation, so the image is never rotated. (Note: the UIImage rotateImage(by:) is a custom method).
Is there some means to call mapView(_:viewFor:) so that I can update the image annotation for user location when user location changes? Thanks for any help.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isEqual(mapView.userLocation) {
// Annotation image for user location
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: nil)
annotationView.image = UIImage(named: "UserLocationImage.png")?.rotateImage(by: currentCourse)
return annotationView
}
}
Use CLLocationManagerDelegate
https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate
Implement this protocol in your code, and put your mapView function into its locationManager(_:didUpdateLocations:) method.

Get LAT and LONG from tapped overlay in Google Maps

When a user taps an overlay, the following code is triggered:
func mapView(_ mapView: GMSMapView, didTap overlay: GMSOverlay) {
}
I wonder if we can extract the exact LAT and LONG coordinates of the overlay that has been tapped?
Thanks!
To solve this we need the 2 methods together,
So I combined them in a way I hope it will help in this issue:
func mapView(_ mapView: GMSMapView, didTap overlay: GMSOverlay) {
print(overlay)
}
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
print(coordinate)
for polyline in polylines {
if GMSGeometryIsLocationOnPath(coordinate, polyline.path!, true) {
self.mapView(mapView, didTap: polyline)
}
}
for polygon in polygons {
if GMSGeometryContainsLocation(coordinate, polygon.path!, true) {
self.mapView(mapView, didTap: polygon)
}
}
}
if the user clicked at coordinate we will deal with this, Then check if this coordinate is contained in any Polyline or Polygon we had defined before, So we fire the event didTap overlay for that overlay.
Make sure to make polylines and polygons isTappable = false
And but in mind this event will be fired for every overlay tapped even though if they are overlaped, You can put return when the if success to take the first overlay only
You can use didTapAt delegate method for the same.
#wajih, for you did the method didTapAt was called when you tap on landmark names or titles? For me it's not getting called.
For my case the landmark title is above the polygon and the didTapAt method is not getting called, but if i set tappable to true then didTap(overlay) is called perfectly.
If you just want to get the exact coordinates wherever you tapped whether on Overlay or not, then there is another delegate of GMSMapViewDelegate which is called whenever we tap on GoogleMaps. In this delegate you can get the exact coordinates where you tapped on map irrespective of tapping on an overlay.
Swift 3.0
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) {
print(coordinate.latitude)
print(coordinate.longitude)
}
If you want to get the coordinate only on tapping marker, then use this delegate method
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
print(marker.position.latitude)
print(marker.position.longitude)
return true
}
Make sure to make your overlay as non-tappable
overlay.isTappable = false
For reference see here

Swift 2 MKMapViewDelegate rendererForOverlay compiler warning

I am trying to draw a polyline on a map in Swift 2. It all works well, but I get a compiler warning for this code:
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay is MKPolyline {
let polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.redColor()
polylineRenderer.lineWidth = 5
return polylineRenderer
}
return nil
}
This will give me a warning says that 'Result and parameters in mapView (rendererForOverlay) have different optionality than expected by protocol MKMapViewDelegate'
Now, this will compile fine, but it bugs me that the compiler warning is showing.
If I change the first line to
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
by removing the !, the warning will go away but I get an error that the return cannot be nil and the code will not compile anymore.
This is also a follow up to this thread where the same problem was stated but no satisfying answer is available:
Swift 2 MKMapViewDelegate rendererForOverlay optionality
Can anyone shed any light on the correct way to use this function now in Swift 2?
Thanks.
Going by what autocomplete suggests the prototype looks like this:
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer
And apparently there's nothing you can do about it, except for returning return MKPolylineRenderer() where normally you would return nil.
To me it looks like an implementation bug, because here's what the documentation says about the returned object:
The renderer to use when presenting the specified overlay on the map. If you return nil, no content is drawn for the specified overlay object.
I suggest you create a case for it in Apple's bug report
Don't return nil. This is only called for overlays you create, so instead of checking if overlay is MKPolyline, check which of your overlays it is. If you have only one, return the specified polyline renderer without checking which one it is.

Can we get all the annotation views from MKMapView

Is there any direct method or any suggestion on how to get all the annotations from the MKMapView?
You can access map's annotations using its annotations property. Getting views for all annotations may not be always possible as annotations that are not currently visible on the map may have no views associated with them, but for arbitrary annotation you can try to get a view using -viewForAnnotation: method.
So here how you can iterate through all map's annotations and try to access their views:
for (id<MKAnnotation> annotation in mapView.annotations){
MKAnnotationView* anView = [mapView viewForAnnotation: annotation];
if (anView){
// Process annotation view
...
}
}
In swift 3.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let selectedAnnotation = view.annotation
for annotation in mapView.annotations {
let viewI = mapView.view(for: annotation)
if !(viewI?.annotation is MKUserLocation){
if annotation.isEqual(selectedAnnotation) {
viewI?.image = UIImage(named: "focus.png")
showLifeEventView(anotation: view) //did select a point on Map.
}else{
viewI?.image = UIImage(named: "point.png")
}
}
}
}