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

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

Related

MapKit, Set current location manually ? It is possible?

How to display current location from coordinates manually? I am taking coordinates from another GPS device. How to set it manually?
Where the coordinates come from is largely immaterial: as long as you have one you can set the map to display an area including that point. If you want to display the point itself you can add an annotation:
A simple example method to drop a pin and zoom in to its location:
func createAnnotation(from coordinate: CLLocationCoordinate2D, title: String) -> MKPointAnnotation {
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = title
return annotation
}
func dropPinAndZoomIn(to coordinate: CLLocationCoordinate2D){
var spanDelta = 0.035 //the width/height of the map area in degrees
let annotation = createAnnotation(from: coordinate, title: "My Location")
mapView.removeAnnotations(mapView.annotations) //clear any prev annotations
mapView.addAnnotation(annotation)
let span = MKCoordinateSpan(latitudeDelta: spanDelta, longitudeDelta: spanDelta)
let region = MKCoordinateRegion(center: coordinate, span: span)
let displayRegion = mapView.regionThatFits(region) //ensure the region can be displayed in the mapView's view
mapView.setRegion(displayRegion, animated: true)
}

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.

How to delay callout from showing when annotation selected in MKMapView? Swift 4

(This is my first stack overflow question haha)
UPDATE:
From this link - Center MKMapView BEFORE displaying callout
I implemented the solution from Thermometer, however selecting and deselecting the annotation makes it look like my application is glitching.
I think the best way would be to delay the callOut (detail pop up) from appearing by a few seconds so the map has time to move first.
Here is my code:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotation = view.annotation else {
return
}
let currentAnnotation = view.annotation as? MapMarker
Global.currentAnnotation = currentAnnotation
findRelatinoshipLines()
if Global.showLifeStoryLines {
var locations = lifeStoryAnnotations.map { $0.coordinate }
let polyline = MKPolyline(coordinates: &locations, count: locations.count)
Global.finalLineColor = Global.lifeStoryColor
mapView.addOverlay(polyline)
}
if Global.showFatherLines {
var fatherLocations = fatherTreeAnnotations.map { $0.coordinate }
let fatherPolyline = MKPolyline(coordinates: &fatherLocations, count: fatherLocations.count)
Global.finalLineColor = Global.fatherLineageColor
mapView.addOverlay(fatherPolyline)
}
if Global.showMotherLines {
var motherLocations = motherTreeAnnotations.map { $0.coordinate }
let motherPolyline = MKPolyline(coordinates: &motherLocations, count: motherLocations.count)
Global.finalLineColor = Global.motherLineageColor
mapView.addOverlay(motherPolyline)
}
if Global.showSpouseLines {
var locations = spouseAnnotations.map { $0.coordinate }
let polyline = MKPolyline(coordinates: &locations, count: locations.count)
Global.finalLineColor = Global.spouseColor
mapView.addOverlay(polyline)
}
if Global.zoomChange == true {
Global.zoomChange = false
} else {
let currentRegion = mapView.region
let span = currentRegion.span
let location = currentAnnotation!.coordinate
let region = MKCoordinateRegion(center: location, span: span)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
mapView.setCenter(annotation.coordinate, animated: true)
//mapView.setRegion(region, animated: true)
}
}
}
CONTINUED:
Basically I'm working on a family genealogy application that displays events from relatives on a map.
When I click an annotation (event) the details (who event belongs to, where and when, etc) pops up above with an information button to show the selected person.
I have it set up to set the MKMapView region so that the selected annotation is centered each time a new annotation is clicked.
The problem is when I click an event that is on the edge of the screen, my annotation title/description pops up off centered so that it fits on my screen because it doesn't know that I plan on re-centering the map view around said annotation.
I was wondering if there was any way to make the title/description appear centered directly above the selected annotation so that when I move the map everything is centered and fits on the screen.
Here are some screenshots of what I'm talking about:
Before and After
Solved it by calling setCenter with a slight delay in mapView(_:didSelect:):
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotation = view.annotation else {
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
mapView.setCenter(annotation.coordinate, animated: true)
}
}

How to Place Bottom Of a Custom Annotation On a Specific Coordinate in Mapbox iOS

I'm using iOS Mapbox SDK in my app. I changed image for an annotation to a custom image (It looks like a map marker). When I add an annotation to a specific coordinate on the map view, It will be added but the center of my custom annotation image (the marker) will be set on the coordinate. I need to change the marker position to set the bottom of the marker on the coordinate. I found a way but I do not know is there a better way or not?
I converted the coordinate to a point, then changed the point y position, then converted the point to a new coordinate.
func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {
let reuseIdentifier = "annotationImage"
var annotationImage = mapView.dequeueReusableAnnotationImage(withIdentifier: reuseIdentifier)
if annotationImage == nil {
annotationImage = MGLAnnotationImage(image: UIImage(named: "Orange")!, reuseIdentifier: reuseIdentifier)
}
return annotationImage
}
func addDestinationMarker(coordinate: CLLocationCoordinate2D) {
guard let mapView = mapView else { return }
if let annotations = mapView.annotations {
mapView.removeAnnotations(annotations)
}
var point = mapView.convert(coordinate, toPointTo: mapView)
point.y -= markerImageView.frame.height / 2
let newCoordinate = mapView.convert(point, toCoordinateFrom: mapView)
let annotation = MGLPointAnnotation()
annotation.coordinate = newCoordinate
mapView.addAnnotation(annotation)
}
I've run into this same issue and started to think that round map pins were becoming the defacto standard so they could just be plonked onto the map with the image centre denoting the coordinate. However if you take a look at this example on the Mapbox website, they use a non-round image and solve the offset problem quite nicely.
// The anchor point of an annotation is currently always the center. To
// shift the anchor point to the bottom of the annotation, the image
// asset includes transparent bottom padding equal to the original image
// height.
//
// To make this padding non-interactive, we create another image object
// with a custom alignment rect that excludes the padding.
image = image.withAlignmentRectInsets(UIEdgeInsets(top: 0, left: 0, bottom: image.size.height/2, right: 0))
This does mean that you need to generate pin images that are twice as tall, with the lower half transparent, but that's really not a big deal.
You can solve this by leveraging the centerOffset property that MGLAnnotationView provides. Though I'm not sure if it's present in the MGLAnnotationImage you're using.
To set the anchor to the bottom of the annotation, use:
centerOffset.dy = -height / 2
If you set the frame beforehand, the height is simply frame.height.
The other answers express things correctly but both are missing the correct syntax:
annotationView?.centerOffset.y = -(annotationView?.frame.height ?? 0) / 2
This will achieve the expected result.

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.