I try to create a custom "badget" for my MKPointAnnotation in swift, but it fails as MKPointAnnotation does not have any property like image
var information = MKPointAnnotation()
information.coordinate = location
information.title = "Test Title!"
information.subtitle = "Subtitle"
information.image = UIImage(named: "dot.png") //this is the line whats wrong
Map.addAnnotation(information)
Anyone figured out a swift like solution for that?
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is MKPointAnnotation) {
return nil
}
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier("demo")
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "demo")
annotationView!.canShowCallout = true
}
else {
annotationView!.annotation = annotation
}
annotationView!.image = UIImage(named: "image")
return annotationView
}
Related
I am showing 3 distinct annotations in a map. To achive this I have a enum as a class variable which indicates the current value of the image name to be set in the MKAnnotationView property. I have subclassed MKAnnotationView to sore a class variable to get the image name in case of annotation reuse.
The problem is that when I drag the map leaving the annotations out of view and when I drag it again to see the annotations, these have their images exchanged.
My enum and custom MKAnnotationView class:
enum AnnotationIcon: String {
case taxi
case takingIcon
case destinyIcon
}
final class MyMKAnnotationView: MKAnnotationView {
var iconType: String = ""
}
And this is my viewFor annotation function:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else {
return nil
}
let identifier = "Custom annotation"
var annotationView: MyMKAnnotationView?
guard let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MyMKAnnotationView else {
let av = MyMKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView = av
annotationView?.annotation = annotation
annotationView?.canShowCallout = true
annotationView?.translatesAutoresizingMaskIntoConstraints = false
annotationView?.widthAnchor.constraint(equalToConstant: 35).isActive = true
annotationView?.heightAnchor.constraint(equalToConstant: 35).isActive = true
annotationView?.iconType = annotationIcon.rawValue //AnnotationIcon enum class variable
annotationView?.image = UIImage(named: annotationView?.iconType ?? "")
return av
}
annotationView = dequeuedAnnotationView
annotationView?.image = UIImage(named: annotationView?.iconType ?? "")
return annotationView
}
Images that explain the problem:
Before the draggin:
After the draggin:
What is the way for each annotation to retrieve the correct image in case of reuse?
Thank you.
Before reusing the MyMKAnnotationView you've to empty the already set image in the prepareForReuse method.
final class MyMKAnnotationView: MKAnnotationView {
var iconType: String = ""
//...
override func prepareForReuse() {
super.prepareForReuse()
image = nil // or set a default placeholder image instead
}
}
Update: As suspected the iconType is not getting set before you're trying to set the image of MyMKAnnotationView. Either you need to set the iconType before setting the image, like this:
annotationView?.iconType = AnnotationIcon.taxi.rawValue
annotationView?.image = UIImage(named: annotationView?.iconType ?? "")
You can improve this a lot by being the image returning logic to the AnnotationIcon.
enum AnnotationIcon: String {
//...
var annotationImage: UIImage? { UIImage(named: rawValue) }
}
Then change the MyMKAnnotationView as follows:
final class MyMKAnnotationView: MKAnnotationView {
var iconType = AnnotationIcon.taxi {
didSet {
image = iconType.annotationImage // image is set whenever `iconType` is set
}
}
//...
}
Then in viewForAnnotation:
var iconTypes = [AnnotationIcon]() // should be equal to the number of annotation
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
//...
dequeuedAnnotationView.annotation = annotation
dequeuedAnnotationView.iconType = iconType
}
Your problem is that you only set the iconType property when you create the annotation view. When an annotation view is reused, you set the image based on that property rather the current annotation.
Really, there is no need for the iconType property. You should just always use an icon value from your annotation. The annotation is the data model.
You also don't set the view's annotation property correctly in the case of reuse.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let myAnnotation = annotation as? MyAnnotation else {
return nil
}
let identifier = "Custom annotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MyMKAnnotationView
if annotationView == nil {
annotationView = MyMKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView?.canShowCallout = true
annotationView?.translatesAutoresizingMaskIntoConstraints = false
annotationView?.widthAnchor.constraint(equalToConstant: 35).isActive = true
annotationView?.heightAnchor.constraint(equalToConstant: 35).isActive = true
}
annotationView?.annotation = MyAnnotation
annotationView?.image = UIImage(named: MyAnnotation.icon.rawValue)
return annotationView
}
I have solved this issue subclassing the MKPointAnnotation to know what type of Annotation I am reusing:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else {
return nil
}
var annotationView: MKAnnotationView?
switch annotation {
case is MyCustomTaxiAnnotation:
annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationTaxiID, for: annotation)
case is MyCustomOriginAnnotation:
annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationOriginID, for: annotation)
case is MyCustomDestinyAnnotation:
annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationDestinyID, for: annotation)
default:
annotationView = nil
}
if annotationView == nil {
switch annotationIcon {
case .destinyIcon:
annotationView = MyDestinyView(annotation: annotation, reuseIdentifier: annotationDestinyID)
annotationView?.annotation = MyCustomDestinyAnnotation()
case .takingIcon:
annotationView = MyOriginView(annotation: annotation, reuseIdentifier: annotationOriginID)
annotationView?.annotation = MyCustomOriginAnnotation()
case .taxi:
annotationView = MyTaxiView(annotation: annotation, reuseIdentifier: annotationTaxiID)
annotationView?.annotation = MyCustomTaxiAnnotation()
}
} else {
annotationView?.annotation = annotation
}
return annotationView
}
For example, the Taxi annotation is a subclass of MKPointAnnotation:
let annotation = MyCustomTaxiAnnotation()
final class MyCustomTaxiAnnotation: MKPointAnnotation {
///...
}
So, taking in account that, I am able to reuse a custom annotation view properly. I have also register the custom MKAnnotationView:
map.register(MyOriginView.self, forAnnotationViewWithReuseIdentifier: annotationOriginID)
map.register(MyDestinyView.self, forAnnotationViewWithReuseIdentifier: annotationDestinyID)
map.register(MyTaxiView.self, forAnnotationViewWithReuseIdentifier: annotationTaxiID)
What I'm trying to do is to display a custom annotation view on a map view (MKMapView), that is working fine in the app.
The problem is when I'm switching between apps or changing between dark & light modes the custom annotation view seems to revert to the default annotation pin as shown below.
Deployment Target: iOS 11
Running on: iPhone XS MAX iOS 13.3
Xcode Version: 11.3 (11C29)
Swift Version: 5.0
private func addAnnotations(_ places : [QPPlaceDM]) {
mapView.removeAnnotations(myAnnotations)
var annotations = [MKPointAnnotation]()
for item in places {
let annotation = QPCustomAnnotaion()
annotation.title = item.name
annotation.coordinate = item.coordinates
annotation.image = item.category.pinImage
annotations.append(annotation)
}
myAnnotations = annotations
mapView.addAnnotations(myAnnotations)
}
.......
extension QPMainVC : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is QPCustomAnnotaion) {
return nil
}
let annotationID = "AnnotationId"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationID)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotationID)
annotationView?.canShowCallout = true
}
else {
annotationView?.annotation = annotation
}
let myCustomAnnotation = annotation as? QPCustomAnnotaion
annotationView?.image = myCustomAnnotation?.image
annotationView?.displayPriority = .required
return annotationView
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotationLocation = view.annotation?.coordinate else { return }
var indexOfAnnotation = 0
for (index , item) in myAnnotations.enumerated() {
if annotationLocation.latitude == item.coordinate.latitude{
indexOfAnnotation = index
break
}
}
let indexPathOfMagorCell = IndexPath(row: indexOfAnnotation, section: 0)
placesCollection.scrollToItem(at: indexPathOfMagorCell, at: .centeredHorizontally, animated: true)
}
}
Working properly
After app switching
Switching from MKPinAnnotationView to MKAnnotationView in the viewFor annotation func fixed this issue for me:
Old:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is CustomMapItemAnnotation) { return nil }
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "id")
if (annotation is CustomMapItemAnnotation) {
annotationView.canShowCallout = true
if let placeAnnotation = annotation as? CustomMapItemAnnotation {
if let type = placeAnnotation.type {
print("\(type)-color")
annotationView.image = UIImage(named: "\(type)-color")
}
}
}
return annotationView
}
New:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is CustomMapItemAnnotation) { return nil }
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "id")
if (annotation is CustomMapItemAnnotation) {
annotationView.canShowCallout = true
if let placeAnnotation = annotation as? CustomMapItemAnnotation {
if let type = placeAnnotation.type {
print("\(type)-color")
annotationView.image = UIImage(named: "\(type)-color")
}
}
}
return annotationView
}
I am working in map view annotation.
The marker annotation should be displayed using the parking rule type
If paid pin image be "paid" and if free pin image be "free"
I am getting all annotation as "paid" image
I have attached my code below can any one help me in this issue to fix
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Don't want to show a custom image if the annotation is the user's location.
guard !(annotation is MKUserLocation) else {
return nil
}
// Better to make this class property
let annotationIdentifier = "AnnotationIdentifier"
var annotationView: MKAnnotationView?
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) {
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
}
else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
if let annotationView = annotationView {
// Configure your annotation view here
if parkingTypeArray.count > 0 {
for cameraa in parkingTypeArray.enumerated() {
if cameraa.element == "Free street parking" {
let pinImage = UIImage(named: "free")
annotationView.image = pinImage
}else if cameraa.element == "Paid street parking" {
let pinImage = UIImage(named: "paid")
annotationView.image = pinImage
}else if cameraa.element == "Paid parking" {
let pinImage = UIImage(named: "paid")
annotationView.image = pinImage
}
}
}
}
return annotationView
}
Same thing I Have Done with Custom MKPointAnnotation Class
class MyPointAnnotation : MKPointAnnotation {
var obj : ComparableData?
init(data_obj : ComparableData) {
self.obj = data_obj
super.init()
}
}
Setup Map
for item in self.Arr_Map_Comparables{
if item.Latitude != "" && item.Longitude != ""{
let annotation = MyPointAnnotation(data_obj: item)
annotation.coordinate = CLLocationCoordinate2D(latitude: Double(item.Latitude!)!, longitude: Double(item.Longitude!)!)
annotation.title = item.Full_Address
mapView.addAnnotation(annotation)
}
}
self.focusMarkers(markers: mapView.annotations, width: 50)
MapView Delegate Methods
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
// Don't want to show a custom image if the annotation is the user's location.
guard !(annotation is MKUserLocation) else {
return nil
}
// Better to make this class property
let annotationIdentifier = "AnnotationIdentifier"
var annotationView: MKAnnotationView?
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) {
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
}
else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
}
if let annotationView = annotationView {
// Configure your annotation view here
annotationView.canShowCallout = true
if let annotation = annotationView.annotation as? MyPointAnnotation{
if annotation.obj?.Status_Mls == "Active"{
annotationView.image = UIImage(named: "active")
}else if annotation.obj?.Status_Mls == "Sold"{
annotationView.image = UIImage(named: "sold")
}else{
annotationView.image = UIImage(named: "other")
}
}
}
return annotationView
}
I am currently getting some locations from a web request using alamofire, store them in my Object array and then display them on a MKMapView.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else {
return nil
}
let annotationIdentifier = "AnnotationIdentifier"
var annotationView: MKAnnotationView?
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) {
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
}
else {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
}
if let annotationView = annotationView {
annotationView.canShowCallout = true
annotationView.image = UIImage(named: "atm_map_icon")
}
return annotationView
}
And here is where i parse the json, put the result in an array and the loop through the array and add the markers to the map
for var i in 0..<json.count{
let name = json[i]["Name"].string
let address = json[i]["Address"].string
let lat = json[i]["Lat"].double
let lon = json[i]["Lon"].double
let atm = Atm(name: name!,address: address!,lat: lat!,lon: lon!)
self.atmArrayList.append(atm)
}
print(self.atmArrayList.count)
}
for atm in self.atmArrayList{
let atmPin = AtmAnnotation(title: atm.name!, subtitle: atm.address!, atm: atm, coordinate: CLLocationCoordinate2D(latitude: atm.lat!, longitude: atm.lon!))
self.annotationArray.append(atmPin)
}
self.atmMapView.addAnnotations(self.annotationArray)
What i want to achieve is so when the user clicks on the rightCalloutAccessoryView button, i want to pass the specific Atm object of that Annotation to the another ViewController.
My guess is to give the Annotations an id and then get the Atm from the Atm array in that specific position??
You need to implement this method of MapViewDelegate in your viewController
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let anotherViewController = self.storyboard?.instantiateViewController(withIdentifier: "anotherViewController") as! AnotherViewController
if let atmPin = view.annotation as? AtmAnnotation
{
anotherViewController.currentAtmPin = atmPin
}
self.navigationController?.pushViewController(anotherViewController, animated: true)
}
Hope this helps
I am trying to rotate an image for a MKAnnotation and while I succeed to do so, the title of it is also rotating. Is there a way to keep the title straight? Any help would be really appreciated!
Here is my code:
In viewDidLoad():
let middlePoint = CustomPointAnnotation()
middlePoint.coordinate = self.coordinates
middlePoint.imageName = "routemiddle"
middlePoint.title = "\(hourDetailedRoute):\(minuteDetailedRoute)"
middlePoint.courseDegrees = self.vehicleChangeCourse
self.mapa.addAnnotation(middlePoint)
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is CustomPointAnnotation) {
return nil
}
let reuseId = "annotation"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView!.canShowCallout = true
}
else {
anView!.annotation = annotation
}
let cpa = annotation as! CustomPointAnnotation
anView!.image = UIImage(named:cpa.imageName)
anView!.transform = CGAffineTransformRotate(self.mapa.transform, CGFloat(degreesToRadians(cpa.courseDegrees)))
return anView
}
class CustomPointAnnotation: MKPointAnnotation {
var imageName: String!
var courseDegrees = 0.0
}
I have figured it out! I just needed to rotate only the image instead of rotating the whole view.
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is CustomPointAnnotation) {
return nil
}
let reuseId = "annotation"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView!.canShowCallout = true
}
else {
anView!.annotation = annotation
}
let cpa = annotation as! CustomPointAnnotation
var imagePin = UIImage(named:cpa.imageName)
imagePin = imagePin?.rotateImage(cpa.courseDegrees)
anView!.image = imagePin
return anView
}