I have to add two pin annotation - swift

I have to change image of my pin annotation in MapKit. And there should be two pin. One of these should be current location, second one is selector new location
func mapView(\_ mapView: MKMapView, viewFor annotation: MKAnnotation) -\> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "customannotation")
annotationView.image = UIImage(named:"pin")
annotationView.canShowCallout = true
return annotationView
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
print("annotation title == \(String(describing: view.annotation?.title!))")
}

You said:
I have to change image of my pin annotation in MapKit.
If you are saying that you have an annotation showing its associated annotation view on the map, and you want to change the image for that annotation view, you can add a property to your annotation to indicate which image is to be shown, and then have a custom MKAnnotationView object which observes the annotation’s property and updates its image, accordingly.
For example, consider an annotation that has an imageName property:
class CustomAnnotation: MKPointAnnotation {
#objc dynamic var imageName: String
init(
coordinate: CLLocationCoordinate2D,
title: String? = nil,
subtitle: String? = nil,
imageName: String
) {
self.imageName = imageName
super.init()
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
Note, I made that an #objc dynamic property so that I can have an annotation view that observes that image name change:
class CustomAnnotationView: MKAnnotationView {
static let reuseIdentifier = "CustomAnnotationView"
override var annotation: MKAnnotation? {
didSet { addObserver(for: annotation) }
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
addObserver(for: annotation)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var observer: NSKeyValueObservation?
func addObserver(for annotation: MKAnnotation?) {
let annotation = annotation as? CustomAnnotation
let imageName = annotation?.imageName ?? "red"
updateImage(named: imageName)
observer = annotation?.observe(\.imageName) { [weak self] annotation, _ in
self?.updateImage(named: annotation.imageName)
}
}
private func updateImage(named name: String) {
let image = UIImage(named: name)
self.image = image
// the following is only needed if you want the bottom of the
// image over the coordinates (e.g., you are using a “pin”).
if let image = image {
centerOffset = CGPoint(x: 0, y: -image.size.height / 2)
}
}
}
So, you can create the annotation:
let annotation = CustomAnnotation(coordinate: coordinate, imageName: "red")
mapView.addAnnotation(annotation)
And when you want, you can update the imageName associated for that annotation, and the annotation view will observe that value change, and update the image accordingly:
annotation.imageName = "black"
That yields:
And there should be two pin. One of these should be current location, second one is selector new location
There is a special annotation for the user location, the MKUserLocation. Right now, your mapView(_:viewFor:) is returning nil for this annotation. Just change your viewFor to return whatever annotation you want for the MKUserLocation rather than nil.
Obviously this assumes (a) that you have enabled showsUserLocation property for your mapView and (b) that you have requested permission for the user’s location. E.g.:
private let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
if manager.authorizationStatus == .notDetermined {
manager.requestWhenInUseAuthorization()
}
mapView.showsUserLocation = true
...
}
And of course, set the appropriate privacy strings in Info.plist:

Related

Clustering doesn't work properly in iOS 13

I am doing clustering of Annotations.
The below code works fine and clusters the points correctly in iOS 11 & iOS 12.
This fails to cluster the points decluster the points in iOS 13.
I am not using any beta versions.
The TTMapView class is wrapper for MKMapView.
class TTMapView: UIView {
var mapView = MKMapView()
private var mapObjects: Dictionary<TTShape, MKShape?> = [:]
private var _isClusteringEnabled = true
func addMarker(_ marker: TTPoint) -> TTPoint {
removeMarker(marker)
let coordinate = marker.coordinate
let pointAnnotation = MKPointAnnotation()
pointAnnotation.coordinate = convertTTCoordinateToCLLocationCoordinate2D(coordinate)
pointAnnotation.title = marker.title
pointAnnotation.subtitle = marker.subtitle
mapObjects.updateValue(pointAnnotation, forKey: marker)
mapView.addAnnotation(pointAnnotation)
return marker
}
}
extension TTMapView: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
if _isClusteringEnabled {
let point = mapObjects.filter ({ $0.value === annotation }).first?.key as? TTPoint
print("point ", point)
return TTClusterAnnotationView(annotation: annotation, reuseIdentifier: TTClusterAnnotationView.ReuseID, image: point?.image, color: point?.tintColor)
} else {
let reuseId = "simplePin"
var pinAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if pinAnnotationView == nil {
pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinAnnotationView?.isDraggable = true
pinAnnotationView?.canShowCallout = true
}
return pinAnnotationView
}
}
}
class TTClusterAnnotationView: MKMarkerAnnotationView {
/// Use this Id for setting annotation
static let ReuseID = "clusterAnnotation"
init(annotation: MKAnnotation?, reuseIdentifier: String?, image: UIImage?, color: UIColor? = nil) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
// Enable clustering by just setting the clusteringIdentifier
clusteringIdentifier = "clusteringIdentifier"
glyphImage = image
glyphTintColor = color
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForDisplay() {
super.prepareForDisplay()
displayPriority = .required
}
}
Make the cluster annotation view (this is the annotation view for the cluster, not to be confused with your existing annotation view that has a clusteringIdentifier, which I’ve renamed to CustomAnnotationView to avoid confusion) have a displayPriority of .required:
class CustomClusterAnnotationView: MKMarkerAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
displayPriority = .required
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var annotation: MKAnnotation? {
didSet {
displayPriority = .required
}
}
}
Then register that class:
mapView.register(CustomClusterAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
And then use it:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
if annotation is MKClusterAnnotation {
return nil
}
...
}
A few unrelated observations:
I’d suggest just adding your image and color properties to a custom annotation type rather than having viewFor filter through mapObjects. So:
class CustomAnnotation: MKPointAnnotation {
let image: UIImage
let color: UIColor
init(coordinate: CLLocationCoordinate2D, title: String?, image: UIImage, color: UIColor) {
self.image = image
self.color = color
super.init()
self.coordinate = coordinate
self.title = title
}
}
Then, if you use that rather than MKPointAnnotation, your custom annotation view can pull the color and image info right out of the annotation.
class CustomAnnotationView: MKMarkerAnnotationView {
static let reuseID = "CustomAnnotationView"
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
updateForAnnotation()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var annotation: MKAnnotation? {
didSet {
updateForAnnotation()
}
}
}
private extension CustomAnnotationView {
func updateForAnnotation() {
clusteringIdentifier = "CustomAnnotationView"
displayPriority = .required
if let annotation = annotation as? CustomAnnotation {
glyphImage = annotation.image
glyphTintColor = annotation.color
} else {
glyphImage = nil
glyphTintColor = nil
}
}
}
Note, above I’m resetting the cluster identifier, image, glyph, etc. in the didSet of annotation. This enables the reuse of annotation views. (See next point.)
The reuse logic for your pin annotation view is not correct. And you’re not doing reuse at all if clustering is turned on. If targeting iOS 11 and later, I’d use dequeueReusableAnnotationView(withIdentifier:for:) which takes care of all of this for you. So, I can register this reuse id:
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomAnnotationView.reuseID)
And I’d repeat that process for the “simple pin” annotation view that you show if you turn off clustering.
mapView.register(CustomSimplePinAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomSimplePinAnnotationView.reuseID)
And
class CustomSimplePinAnnotationView: MKPinAnnotationView {
static let reuseID = "CustomSimplePinAnnotationView"
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
isDraggable = true
canShowCallout = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And then your viewFor is simplified:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
switch annotation {
case is MKUserLocation:
return nil
case is MKClusterAnnotation:
return nil
default:
if _isClusteringEnabled {
return mapView.dequeueReusableAnnotationView(withIdentifier: CustomAnnotationView.reuseID, for: annotation)
} else {
return mapView.dequeueReusableAnnotationView(withIdentifier: CustomSimplePinAnnotationView.reuseID, for: annotation)
}
}
}

Adding a title in front/below a custom annotation mapkit swift 4

In my app, I have a mapkit full of annotations and when one is clicked I want a new view to slide up with details on the annotation. Correct me if I'm wrong but I think you need to create a custom annotation in order to implement the didSelect() method.
The problem is that, by default, the custom annotations that pop up don't have the name of what the annotations is, like the default mapkit annotations do, and since I have around 20 annotations at a time, the user has no way of knowing what they are selecting.
Any idea as to how to add a title or label underneath the custom annotation with the name of the annotation? I don't want to make a call out that pops up above the annotation since I'm having the view slide up, filled with the annotations data.
Here is what I have:
extension ViewController : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "pin") as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
annotationView?.animatesDrop = true
annotationView?.canShowCallout = false
} else {
annotationView?.annotation = annotation
}
return annotationView
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
UIView.animate(withDuration: 0.2) {
// the view that will hold the annotations data
self.annotationInfoViewBottomConstraint.constant = 0
self.view.layoutIfNeeded()
}
}
}
As you can see the default annotation has the name of the location underneath, which is what I want but I want it under the "pin" looking custom annotations.
You can create a label like so:
let annotationLabel = UILabel(frame: CGRect(x: -40, y: -35, width: 105, height: 30))
annotationLabel.numberOfLines = 3
annotationLabel.textAlignment = .center
annotationLabel.font = UIFont(name: "Rockwell", size: 10)
// you can customize it anyway you want like any other label
Set the text:
annotationLabel.text = annotation.title!!
And then add to annotation view:
annotationView.addSubview(annotationLabel)
Picture of annotation with label
I also added a background and border by doing:
annotationLabel.backgroundColor = UIColor.white
annotationLabel.layer.cornerRadius = 15
annotationLabel.clipsToBounds = true
You can also change where the label is in respect to the annotation by changing the X and Y when creating the label. Negative is to the left and up, positive right and down.
Simple Solution for Name under Marker and color change:
MapKit Delegate Method: (I have used MKMarkerAnnotationView and use markerTintColor to set color)
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "pin") as? MKMarkerAnnotationView
guard let annotation = annotation as? PlaceAnnotation else {
return annotationView
}
if annotationView == nil {
annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "pin")
} else {
annotationView?.annotation = annotation
}
annotationView?.markerTintColor = annotation.pinTintColor
return annotationView
}
MKAnnotation Custom Class:
class PlaceAnnotation: NSObject, MKAnnotation {
let title: String?
let locationName: String
let discipline: String
let coordinate: CLLocationCoordinate2D
var pinTintColor: UIColor
init(id: String, title: String, locationName: String, discipline: String, coordinate: CLLocationCoordinate2D, pinTintColor: UIColor) {
self.title = title
self.locationName = locationName
self.discipline = discipline
self.coordinate = coordinate
self.pinTintColor = pinTintColor
super.init()
}
}
How to Use:
let place = PlaceAnnotation(title: "My Location", locationName: "", discipline: "", coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lng), pinTintColor: UIColor.systemBlue)
map.addAnnotation(place)
You'll need to set annotationView.enabled and annotationView.canShowCallout to YES

Swift - MapView intermittently shows annotation title/subtitle as blank

My iOS map view shows an initial annotation. I want the user to be able to click on the map and for the annotation to move to the spot where the user clicked, but to retain the non-coordinate information in the annotation (e.g. title, subtitle). The code below works, but intermittently the title and subtitle are not showing (see animation and that the name "Spot Name" sometimes doesn't show). It's unclear to me why this is occurring. If I add print statements to print spot.name and spot.title after mapView.addAnnotation, the String values are there & remain unchanged in the MKAnnotation conforming class. Also, when I click in the marker, the proper title and subtitle show in the callout, even if they weren't showing in the annotation. Grateful for any advice/corrections. Thanks!
import UIKit
import MapKit
class SpotDetailViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
var spot: Spot! // Spot class conforms to NSObject & MKAnnotation
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
spot = Spot()
spot.coordinate = CLLocationCoordinate2D(latitude: 42.334709, longitude: -71.170061)
spot.name = "Spot Name"
spot.address = "Spot Address, Spot Town, Spot State"
// Set initial region
let regionDistance: CLLocationDistance = 250
let region = MKCoordinateRegionMakeWithDistance(spot.coordinate, regionDistance, regionDistance)
mapView.setRegion(region, animated: true)
mapView.addAnnotation(self.spot)
}
#IBAction func mapViewTapped(_ sender: UITapGestureRecognizer) {
let annotationView = mapView.view(for: mapView.annotations[0])
let touchPoint = sender.location(in: mapView)
guard !(annotationView?.frame.contains(touchPoint))! else {
return
}
let newCoordinate: CLLocationCoordinate2D = mapView.convert(touchPoint, toCoordinateFrom: mapView)
spot.coordinate = newCoordinate
mapView.removeAnnotations(mapView.annotations)
mapView.addAnnotation(self.spot)
mapView.setCenter(spot.coordinate, animated: true)
}
}
extension SpotDetailViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifer = "Marker"
var view: MKMarkerAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifer) as? MKMarkerAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifer)
view.canShowCallout = true
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
return view
}
}

Custom MKPointAnnotation isn't responding to user interaction

I am making a Swift application that uses MKPointAnnotations, and I recently ran into an issue where I needed to store metadata in my annotations, so I created the custom class below:
class BRETTFAnnotation: MKPointAnnotation {
var tag: Int64
var name: String
init(lat : Double, lon:Double, t : Int64, n: String) {
self.tag = t
self.name = n
super.init()
self.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
}
My MKAnnotationView viewfor MKAnnotation method is shown below:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let newAnnotation = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "reuse")
newAnnotation.canShowCallout = true
let right = self.button(title: "Yes")
right?.addTarget(self, action: #selector(clickedToConfirmNewPoint), for: .touchUpInside)
newAnnotation.rightCalloutAccessoryView = right
let left = self.button(title: "No")
left?.addTarget(self, action: #selector(clickedToCancelNewPoint), for: .touchUpInside)
newAnnotation.leftCalloutAccessoryView = left
return newAnnotation
}
The problem I am running into is when ever I click on my custom BRETTFAnnotation (which I add to my MKMapView) nothing happens. When I was just using the MKPointAnnotation (instead of the BRETTFAnnotation) when I clicked on the map the two buttons on the MKAnnotationView would show. I am trying to get the MKPinAnnotationView to show on touch using my BRETTFAnnotation instead of the MKPointAnnotation.
How can I continue to use my custom annotation and show the callout when the user clicks on the annotation at the same time?
Edit 1: Since it is probably useful the code below is how I make the annotation and add it to the mapView.
let location = gestureRecognizer.location(in: mapView)
let coordinate = mapView.convert(location,toCoordinateFrom: mapView)
print("adding lat,long \(coordinate.latitude),\(coordinate.longitude)")
lastPoint = BRETTFAnnotation(lat: coordinate.latitude, lon: coordinate.longitude, t: 1, n: "")
let annotationView = MKPinAnnotationView(annotation: lastPoint, reuseIdentifier: "reuse")
mapView.addAnnotation(lastPoint)
I fix this problem by making my BRETTFAnnotation a subclass of NSObject and MKAnnotation instead of MKPointAnnotation. Doing this allowed my custom class to receive user interaction and show the callouts.
When you use your own MKAnnoation you can handle your actions in didSelect. Just implement the following code.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let yourAnnotation = view.annotation as? BRETTFAnnotation {
//handle your meta data or/and show UIViews or whatever
}
}
with
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
//getting called when you tap on map or on another annotation (not the selected annotation before)
//hide UIViews or do whatever you want
}
That does work for me:
class ViewController: UIViewController, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
print("didSelect")
if let annoation = view.annotation as? MyAnnoation {
print("metatag \(annoation.metaTag)")
}
}
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
print("didDeselect")
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
let annotation = MyAnnoation(n: "name", m: "metaTag")
annotation.coordinate = CLLocationCoordinate2D(latitude: 50.0, longitude: 8.0)
mapView.addAnnotation(annotation)
}
}
class MyAnnoation: MKPointAnnotation {
var name: String?
var metaTag: String?
init(n: String, m: String) {
self.name = n
self.metaTag = m
}
}

Selecting the object at index in a MapView in SWIFT

I am trying to present another view controller once the right callout button is pressed but it must go to the correct indexPath, similar to how this happens in a table view. this is where I am up to:
I have created a custom annotation like so:
class annotationCustom: NSObject, MKAnnotation {
var coordinate : CLLocationCoordinate2D
var title : NSString!
var subtitle : NSString!
var isUserAnnotation : Bool
var dataImage = [NSData]()
init(coordinate: CLLocationCoordinate2D, title: NSString!, subtitle: NSString!){
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
self.isUserAnnotation = false
}
}
and then set the viewForAnnotation like so:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
var dqPin = "pin"
var view = mapView.dequeueReusableAnnotationViewWithIdentifier(dqPin) as? MKPinAnnotationView
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: dqPin)
view!.canShowCallout = true
var arrowBut = UIImage(named: "arrow")
var arrowButton = UIButton()
arrowButton.setImage(arrowBut, forState: UIControlState.Normal)
arrowButton.tintColor = UIColor.blackColor()
arrowButton.frame = CGRectMake(0, 0, 30, 30)
view!.rightCalloutAccessoryView = arrowButton
}
return view
}
Now I know I have to use the function:
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
if control == view.rightCalloutAccessoryView {
let sb = UIStoryboard(name: "Main", bundle: nil)
let userProfileVC = sb.instantiateViewControllerWithIdentifier("usersProfileVC") as UsersProfilesViewController
userProfileVC.profileName = annotationCustom.title // error: 'annotationCustom.Type' does not have a member named 'title'
performSegueWithIdentifier("goToUserFromMap", sender: self)
}
}
}
Can somebody fill in the gaps or help me out please.
Thanks
Instead of:
if control == annotationView.rightCalloutAccessoryView {
it should be:
if control == view.rightCalloutAccessoryView {
view is the internal, private name of the parameter and annotationView is the external name visible to the caller.
However, in this case, you don't really need to check control in the first place since your callouts have only a single control (rightCalloutAccessoryView). They don't have a leftCalloutAccessoryView so there's only one control the user could tap.
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
println("disclosure pressed on: \(view.annotation.title)")
}
By the way, is the code shown in viewForAnnotation the actual code? Because it looks like the result of the dequeueReusableAnnotationViewWithIdentifier is ignored and there is a mysterious extra closing } before the return view.
Here:
userProfileVC.profileName = annotationCustom.title
annotationCustom is the name of your class -- not the instance of it (also, convention is to start class names with a capital letter). view.annotation is the instance here. Cast it as annotationCustom to access any custom properties (though title isn't custom):
if let ac = view.annotation as? annotationCustom {
userProfileVC.profileName = ac.title
}