Customizing annotation pins - swift

I would like to customize my annotations (pins) so I would be able to change its image. I used a function (mapview) to change the picture, so I don’t know if I should use it again to change the picture of another group of pins; let´s say I have two locations, a restaurant and a hospital, so I want to put a plate and a red cross respectively. Here is my function code:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "identifier")
if annotationView == nil{
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "identifier")
annotationView!.canShowCallout = true
annotationView!.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
let pinImg = UIImage(named: "curz")
let size = CGSize(width: 50, height: 50)
UIGraphicsBeginImageContext(size)
pinImg!.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
annotationView!.image = resizedImage
}
else {
annotationView!.annotation = annotation
}
return annotationView
}

You can return different annotation views for different places by comparing the coordinate information. ex:
if ((annotation.coordinate.latitude == theCoordinate1.latitude)
&& (annotation.coordinate.longitude == theCoordinate1.longitude))
{
return someAnnotationView
}
follow this link Adding different images to different annotation views in xcode

The correct way to return different views for different annotation types (or groups of different annotation types) is to subclass the annotation and assign a different reuse identifier for each one (during init). This identifier is similar to the table view cell identifier and is used to reuse (rather than create from scratch) a cell when it scrolls out of sight. The same is true for annotations, rather than create a new one each time a 'discarded' annotation can be reused.
Then in mapview viewfor annotation you can test for annotation.identifier And assign the correct image.

Related

How I automatically rotate annotationView image following map rotating in MKMapKit?

My code does rotate annotationViewImage, when the map rotated.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "mark")
annotationView.image = UIImage(named: "pinArrow")
let ang = mapView.camera.heading
let radi = Double.pi * Double(ang / 360)
let resultAng = 2 * Float(radi)
annotationView.transform = CGAffineTransform(rotationAngle: CGFloat(-resultAng))
annotationView.canShowCallout = true
return annotationView
}
above code, when the map rotates, do not implement the above method.
So add below code
func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
print("visible")
let anns = mapView.annotations
mapView.removeAnnotations(anns)
mapView.addAnnotations(anns)
}
and then, when map rotate, so do annotationView image.
But so a few some slow and loading time a few some long.
Because map moving delegate of above code, so many doing and repeat.
I seek a more simple way without loading slow.
How rotate sync map and annotationView image?

MKPointAnnotation how to hide marker image

I have a MKPointAnnotation that I have setup and it displays where I would like it to however it comes with the default big red pin icon over the dot and I would like to hide that image and display nothing instead. I've tried working with the section I have commented below with "*****" and what I thought might work is setting view.image=nil and this did not change anything and I also tried view.frame.size=CGSize(width: 0, height: 0) which had no effect. Any other suggestions for how to accomplish this?
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotation = annotation
let identifier = "marker"
var view: MKMarkerAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
as? MKMarkerAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
//************************
view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.frame.size=CGSize(width: 0, height: 0)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
return view
}
This was actually easier to solve than I was expecting, all I needed to do was set the markerTintColor to a color with alpha of 0.
view.markerTintColor=UIColor(red: 0.0/255.0, green: 0.0/255.0, blue: 0.0/255.0,alpha:0.5).withAlphaComponent(0)
is what solved it for me in this case.
Here is the apple documentation on the subject:
https://developer.apple.com/documentation/mapkit/mapkit_annotations/annotating_a_map_with_custom_data
They seem to recommend coding a custom Annotation as seen:
class SanFranciscoAnnotation: NSObject, MKAnnotation {
// This property must be key-value observable, which the `#objc dynamic` attributes provide.
#objc dynamic var coordinate = CLLocationCoordinate2D(latitude: 37.779_379, longitude: -122.418_433)
// Required if you set the annotation view's `canShowCallout` property to `true`
var title: String? = NSLocalizedString("SAN_FRANCISCO_TITLE", comment: "SF annotation")
// This property defined by `MKAnnotation` is not required.
var subtitle: String? = NSLocalizedString("SAN_FRANCISCO_SUBTITLE", comment: "SF annotation") }
You can try creating a custom annotation in this way with a clear color or with a size of (0,0). Assign your view to the custom annotation you code.
var view: SanFranciscoAnnotation
If this doesn't work, I will dig up some old code from an app I wrote with custom annotations and share that.

annotation Display Priority doesn't do what I expect, how to keep custom pin on screen

I use the code below on view did load to add a custom annotation icon for the map center that the user started at so that if they scroll away they can always see their starting point.
if let lat = curBlip.blip_lat, let lon = curBlip.blip_lon {
let mapCenter = CLLocationCoordinate2DMake(lat, lon)
let mapSpan = MKCoordinateSpanMake(0.01, 0.01)
let mapRegion = MKCoordinateRegionMake(mapCenter, mapSpan)
self.map.setRegion(mapRegion, animated: true)
let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = "Your Blips Location"
annotation.subtitle = "Subtitle Placeholder"
self.map.addAnnotation(annotation)
When the view loads I load that annotation so its always first and I set a bool named "set" to true after the first annotation to ensure that it gets the custom icon. The issue I am having is that even though I have the annotation set to display priority required the annotation disappears when I move the map away. How can I make that annotation always persist or is there a better way to set a "this is where the map started" circle that doesn't go away?
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if set {
return nil
} else {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "annotationId")
view.image = UIImage(named: "locationArea50")
view.canShowCallout = true
view.displayPriority = .required
set = true
return view
}
Then after I scroll the map away a little bit, I suspect scrolling far enough that the system has to make them reappear the annotation disappears. I assume this has to do with how the grouping of annotations works but that blue annotation is special and I want it to always be present, which is what I thought displayPriority did.
The default value of displayPriority is .required .
So for correct overlapping you need to downgrade priority of red annotations:
redAnnotation.displayPriority = .defaultHigh

Swift: Add Directions button to pin annotation

I have an app that displays the company location using an MKMapView. When the user visits the contact page a map appears at the top of the screen where a pin drops and it shows our location. When the map pin is clicked it shows the company name in the annotation.
I am pulling the company address into the MKMApView from data stored in my Parse backend using back4app.
I have this all working perfectly, however, when I click the map pin and it shows the company name, I am trying to get a car icon button in there also so the user can get directions to our company.
Here is the code I have to set the annotation:
func addPinOnMap(_ address: String) {
var hotelClass = PFObject(className: HOTEL_CLASS_NAME)
hotelClass = hotelArray[0]
if mapView.annotations.count != 0 {
annotation = mapView.annotations[0]
mapView.removeAnnotation(annotation)
}
// Make a search on the Map
localSearchRequest = MKLocalSearchRequest()
localSearchRequest.naturalLanguageQuery = address
localSearch = MKLocalSearch(request: localSearchRequest)
localSearch.start { (localSearchResponse, error) -> Void in
// Add PointAnnonation text and a Pin to the Map
self.pointAnnotation = MKPointAnnotation()
self.pointAnnotation.title = "\(hotelClass[HOTEL_NAME]!)"
self.pointAnnotation.coordinate = CLLocationCoordinate2D( latitude: localSearchResponse!.boundingRegion.center.latitude, longitude:localSearchResponse!.boundingRegion.center.longitude)
self.pinView = MKPinAnnotationView(annotation: self.pointAnnotation, reuseIdentifier: nil)
self.mapView.centerCoordinate = self.pointAnnotation.coordinate
self.mapView.addAnnotation(self.pinView.annotation!)
// Zoom the Map to the location
self.region = MKCoordinateRegionMakeWithDistance(self.pointAnnotation.coordinate, 1000, 1000);
self.mapView.setRegion(self.region, animated: true)
self.mapView.regionThatFits(self.region)
self.mapView.reloadInputViews()
}
}
I simply do not know how I can get the button icon in the annotation and show directions upon click of the button. either using the current map view or to open the users maps app on their device, either way is fine.
Thank you.
I propose you a sample method to had a button on your pin and to give to the user the direction of the pin with the apple map app.
First, after your pin's declaration, you should declare a button witch give you the opportunity to interact with the user in the pin (we will show later how to initialize the button in the pin).
You can declare your button like this:
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPointZero, size: smallSquare))
Note, smallSquare give to the button the correct size of your future pin's button.
You can add a image in the pin's button with the method: button.setBackgroundImage(UIImage(named: "car"), forState: .Normal).
Apple developer give to us some documentation: Apple developer web site.
Then you can had a button action with this method: button.addTarget(self, action: #selector(ViewController.getDirections), forControlEvents: .TouchUpInside).
So, like this, if the button is selected normally (.TouchUpInside), the program calls the function getDirection ().
Now, you can initialize the button in your pin with the method: pinView?.leftCalloutAccessoryView = button.
Note this method set the button in the left of the pin. You can put the button in the right like this: pinView?.rightCalloutAccessoryView = button.
Apple developer give some informations on the subject.
However, I do not know how to initialize the button in the totally surface of the pin.
Finally, you should have the function getDirections ()to give to the user the "direction" of the pin.
I propose you to use the apple map app for this purpose.
My function getDirections () look like this:
func getDirections(){
guard let selectedPin = selectedPin else { return }
let mapItem = MKMapItem(placemark: selectedPin)
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
mapItem.openInMapsWithLaunchOptions(launchOptions)
print ("GET DIRECTION")
}
I find this function in this web site. I think it can be explain this better than me.
At the end my program looks like this:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView?{
guard !(annotation is MKUserLocation) else { return nil }
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
}
pinView?.pinTintColor = UIColor.orangeColor()
pinView?.canShowCallout = true
//The initialization of the pin. Here your program looks great.
// And after the code as seen above.
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPointZero, size: smallSquare))
button.setBackgroundImage(UIImage(named: "car"), forState: .Normal)
button.addTarget(self, action: #selector(ViewController.getDirections), forControlEvents: .TouchUpInside)
pinView?.leftCalloutAccessoryView = button
return pinView
}
func getDirections(){
guard let selectedPin = selectedPin else { return }
let mapItem = MKMapItem(placemark: selectedPin)
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
mapItem.openInMapsWithLaunchOptions(launchOptions)
print ("GET DIRECTION")
}
Normally, your program should look like this:
func addPinOnMap(_ address: String) {
var hotelClass = PFObject(className: HOTEL_CLASS_NAME)
hotelClass = hotelArray[0]
if mapView.annotations.count != 0 {
annotation = mapView.annotations[0]
mapView.removeAnnotation(annotation)
}
// Make a search on the Map
localSearchRequest = MKLocalSearchRequest()
localSearchRequest.naturalLanguageQuery = address
localSearch = MKLocalSearch(request: localSearchRequest)
localSearch.start { (localSearchResponse, error) -> Void in
// Add PointAnnonation text and a Pin to the Map
self.pointAnnotation = MKPointAnnotation()
self.pointAnnotation.title = "\(hotelClass[HOTEL_NAME]!)"
self.pointAnnotation.coordinate = CLLocationCoordinate2D( latitude: localSearchResponse!.boundingRegion.center.latitude, longitude:localSearchResponse!.boundingRegion.center.longitude)
self.pinView = MKPinAnnotationView(annotation: self.pointAnnotation, reuseIdentifier: nil)
self.mapView.centerCoordinate = self.pointAnnotation.coordinate
self.mapView.addAnnotation(self.pinView.annotation!)
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPointZero, size: smallSquare))
button.setBackgroundImage(UIImage(named: "car"), forState: .Normal)
button.addTarget(self, action: #selector(ViewController.getDirections), forControlEvents: .TouchUpInside)
self.pinView?.leftCalloutAccessoryView = button
// Zoom the Map to the location
self.region = MKCoordinateRegionMakeWithDistance(self.pointAnnotation.coordinate, 1000, 1000);
self.mapView.setRegion(self.region, animated: true)
self.mapView.regionThatFits(self.region)
self.mapView.reloadInputViews()
}
func getDirections(){
guard let selectedPin = selectedPin else { return }
let mapItem = MKMapItem(placemark: selectedPin)
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
mapItem.openInMapsWithLaunchOptions(launchOptions)
print ("GET DIRECTION")
}
Excuse me, I can not try your code because I have not your totally code like the pinView declaration.
Note, you can need to add this line of code to accept than the pin can show callout pinView?.canShowCallout = true.
You can consult this webs sites if you want more details or tell me a comment.
Thorn technologies
Apple developer

How add description to MKPolyline & MKPolygon?

How add annotations to polyline and polygon in Swift & MapKit? By Point is simple.
S.,
I'm not sure what you're asking here, but I assume you want to display an annotation somewhere on the polyline.
First the intro how to get the the polyline:
So, lets assume you have an array of CLLocation objects that will draw the polyline on the map. We call this array of location objects: myLocations and it's of type [CLLocation]. Now somewhere in your app you call a method that creates the polyline, we call this method createOverlayObject(locations: [CLLocation]) -> MKPolyline.
Your call could look like this:
let overlayPolyline = createOverlayObject(myLocations)
The method you called then could look like this:
func createOverlayObject(locations: [CLLocation]) -> MKPolyline {
//This method creates the polyline overlay that you want to draw.
var mapCoordinates = [CLLocationCoordinate2D]()
for overlayLocation in locations {
mapCoordinates.append(overlayLocation.coordinate)
}
let polyline = MKPolyline(coordinates: &mapCoordinates[0], count: mapCoordinates.count)
return polyline
}
This was the first part, don't forget to implement the mapView(_: rendererForOverlay overlay:) to get the line rendered. this part could look something like this:
func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
//This function creatss the renderer for the polyline overlay. This makes the polyline actually display on screen.
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = mapLineColor //The color you want your polyline to be.
renderer.lineWidth = self.lineWidth
return renderer
}
Now the second part get the annotation somewhere on the map. This is actually straight forward if you know what the coordinates are where you want to put your annotation. creating and displaying the annotation is straightforward again, assuming you have defined a map view called myNiceMapView:
func createAnnotation(myCoordinate: CLLocationCoordinate2D) {
let myAnnotation = MKPointAnnotation()
myAnnotation.title = "My nice title"
startAnnotation.coordinate = myCoordinate
self.myNiceMapView.addAnnotations([myAnnotation])
}
Don't forget to implement mapView(_: MKMapView, viewForAnnotation annotation:) -> MKAnnotationView? method, which might look like:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
//This is the mapview delegate method that adjusts the annotation views.
if annotation.isKindOfClass(MKUserLocation) {
//We don't do anything with the user location, so ignore an annotation that has to do with the user location.
return nil
}
let identifier = "customPin"
let trackAnnotation = MKAnnotationView.init(annotation: annotation, reuseIdentifier: identifier)
trackAnnotation.canShowCallout = true
if annotation.title! == "Some specific title" { //Display a different image
trackAnnotation.image = UIImage(named: "StartAnnotation")
let offsetHeight = (trackAnnotation.image?.size.height)! / 2.0
trackAnnotation.centerOffset = CGPointMake(0, -offsetHeight)
} else { //Display a standard image.
trackAnnotation.image = UIImage(named: "StopAnnotation")
let offsetHeight = (trackAnnotation.image?.size.height)! / 2.0
trackAnnotation.centerOffset = CGPointMake(0, -offsetHeight)
}
return trackAnnotation
}
Now the challenges is finding the right coordinate where to put your annotation. I can't find anything better than that you have a CLLocationCoordinate2D that references the location you want to put the annotation. Then with a for-in loop find the location where you want to put your annotation, something like this:
for location in myLocations {
if (location.latitude == myReferenceCoordinate.latitude) && (location.longitude == myReferenceCoordinate.longitude) {
self.createAnnotation(location: CLLOcationCoordinate2D)
}
}
Hope this answers your question.