Annotations showing wrong image - swift

Is there any way that annotations might show other images then it should supose ? Is it a way to fix that ?
I've got a werid problem. After my clusters going to evolve to the single annotations then after updating ( zoom out - zoom in ) they images going to be diferent then it should be. After debuging it says that the color of the pin view image is the one which I suposed. Where I could hook up yet to see the exact data ? Maybe something after viewFor which get invoked.
Here is a logic how am I doing this.
The dictionary to knows witch model is on the top of the added annotations.
var dicAnnotations = [FBAnnotation : MyModel]()
Through the loop of [MyModel] i am appending data to the array of the [FBAnnotation] which is needed for saving them via FBClusteringManager. Then in the same time while the new annotation is added to the [FBAnnotation] I am adding
self.dicAnnotations[fbAnnotation] = objModel
to know which annotation have which object.
So as we know how I am soring the data of annotations, how Am I indexing them, lets have a look on the delegate of the MKMapView.
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
DispatchQueue.global(qos: .userInitiated).async {
let mapBoundsWidth = Double(self.mapView.bounds.size.width)
let mapRectWidth = self.mapView.visibleMapRect.size.width
let scale = mapBoundsWidth / mapRectWidth
let annotationArray = self.clusterManager.clusteredAnnotations(withinMapRect: self.mapView.visibleMapRect, zoomScale:scale)
for ann in annotationArray {
if ann is FBAnnotation {
let a = ann as! FBAnnotation
print("Here u are , my color is : \(self.dicAnnotations[a]!.lineColor)")
}
}
DispatchQueue.main.async {
self.clusterManager.display(annotations: annotationArray, onMapView: self.mapView)
}
}
}
The for loop is for checking if the annotation is the one which should be - and it is!
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var reuseId = ""
if annotation is FBAnnotationCluster {
reuseId = "Cluster"
var clusterView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if clusterView == nil {
clusterView = FBAnnotationClusterView(annotation: annotation, reuseIdentifier: reuseId, configuration: FBAnnotationClusterViewConfiguration.default())
} else {
clusterView?.annotation = annotation
}
return clusterView
}
else {
let ann = annotation as! FBAnnotation
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: "Pin")
pinView = MKAnnotationView(annotation: ann, reuseIdentifier: "Pin")
var modelType = dicAnnotations[ann]?.modelType
modelType = modelType?.modelTypeToString(modelType: modelType!)
let modelColor = dicAnnotations[ann]?.lineColor
let modelDirection = dicAnnotations[ann]?.withHeading360
print("\(modelType!)_\(modelColor!)")
pinView?.image = UIImage(named: "\(modelType!)_\(modelColor!)")?.rotated(by: Measurement(value: modelDirection!, unit: .degrees))
return pinView
}
}
How it looks in the stack call which I am able get :
Here u are , my color is : yellow
(lldb) po dicAnnotations[ann]
▿ Optional<MyModel>
▿ some : <Project.MyModel: 0x101b656f0>
(lldb) po dicAnnotations[ann]?.lineColor
▿ Optional<String>
- some : "yellow"
From :
// viewFor
... else { pinView?.annotation = ann } ...
Any ideas ? It shows me would say "random colored image".
Thanks in advance!
Solution
I've edited the delegate viewFor. Deleted the if pinView == null condition and else as well. Somehow it was not working like it suposed to.

Related

iOS MapKit Custom User location heading wrong

I am trying to make a custom "user location" pin, with heading rotation based on users location heading.
I used this answer: https://stackoverflow.com/a/58363556/894671 as a base and managed to get up and running a custom pin, that rotates based on the heading.
The problem:
While testing on a device, it seems, that the transformation using the provided heading is not correct.
Only at 0/360 degrees it shows up correctly, but If I rotate around, I am seeing default MKMapKit shown heading to be correctly rotating, while my custom icon manages to rotate twice in that same time.
Please see the attached video:
https://i.imgur.com/3PEm2MS.mp4
Demo uploaded here:
https://github.com/GuntisTreulands/Demo123
But for all intents and purposes, here is AnnotationView:
class AnnotationView : MKAnnotationView, HeadingDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
func headingChanged(_ heading: CLLocationDirection) {
// For simplicity the affine transform is done on the view itself
UIView.animate(withDuration: 0.1, animations: { [unowned self] in
self.transform = CGAffineTransform(rotationAngle: CGFloat(heading * .pi / 180.0 ))
})
}
}
and heading is forwarded from here:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if let lastLocation = locations.last {
userLocationAnnotation.coordinate = lastLocation.coordinate
}
}
I can't figure out, why my location heading is acting so weird.
Okay, I managed to kinda solve this.
Here is the new result: https://imgur.com/a/YJ1H683
What I did was:
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
if let heading = mapView.userLocation.heading {
userLocationAnnotation.heading = -mapView.camera.heading + (heading.trueHeading > 0 ? heading.trueHeading : heading.magneticHeading)
} else {
userLocationAnnotation.heading = -mapView.camera.heading + (newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading)
}
}
So - in case I am using userlocation with trackingmode == .followWithHeading, this is working great. My icon is on top of the original icon.
To hide original user icon and show yours instead - return a custom annotation view (with nothing to render) for userlocation:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "AnnotationView")
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "AnnotationView")
}
return annotationView
}
if let annotation = annotation as? Annotation {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: NSStringFromClass(Annotation.self))
if (annotationView == nil) {
annotationView = AnnotationView(annotation: annotation as MKAnnotation, reuseIdentifier: NSStringFromClass(Annotation.self))
} else {
annotationView!.annotation = annotation as MKAnnotation
}
annotation.headingDelegate = annotationView as? HeadingDelegate
annotationView!.image = UIImage.init(named: "user_pin")
return annotationView
}
return nil
}
Thus the result is:
1.) Get original apple maps user location + followWithHeading, but with a custom pin and correct location.
2.) I can adjust my custom location pin location to some different coordinates, but keep in mind, that followWithHeading will rotate around the real userLocation coordinates.
3.) I have also noticed, that without userlocation, the original calculation:
userLocationAnnotation.heading = -mapView.camera.heading + (newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading)
Is also .. kinda okay, if I move and rotate. It is faulty, if I just rotate in place. Still cannot solve that one.
I also managed to make a custom followWithHeading, with this function:
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
mapView.centerCoordinate = userLocationAnnotation.coordinate
mapView.camera.heading = (newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading)
userLocationAnnotation.heading = -mapView.camera.heading + (newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading)
}
But the result is not as fluid as the original apple map rotation.
If I rotate fast, then it is fine. But if I slowly turn my device, then the rotation is visibly not smooth.
See the result: https://imgur.com/a/SusV61a

Swift handle did select on multiple annotations on same location

I'm trying to call a function when annotations of the same location are selected. All I'm aiming for is to call a function and show a view controller with the two restaurants but for some reason, the function below isn't being called when I selected the marker in the image. (It works fine for single annotations). Thanks for any help.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let anno = view.annotation as? MyAnno else { return }
print(anno.post)
}
The problem lies in your guard statement. As I could find out in a short research, iOS uses a MKClusterAnnotation type for annotations that are equally located. Thus, the view variable does not have a .annotation set that confirms to your custom subclass.
In this case you need to try to cast it to a MKClusterAnnotation object, which in return has a property .memberAnnotations of type [MKAnnotation], which is what you want.
I think your code should look something like this (haven't tested it though):
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let anno = view.annotation as? MyAnno {
print("Single Annotation selected:")
print(anno.post)
// do anything
return
}
if let anno = view.annotation as? MKClusterAnnotations {
let selection: [MKAnnotation] = anno.memberAnnotations
print("\(selection.count) Annotations selected:")
// do something with your annotation group
// you should now also be able to cast to your MyAnno class again
for item in selection {
if let myAnno = item as? MyAnno {
print(myAnno.post)
}
}
}
}
For further information also take a look at Decluttering a Map with MapKit Annotation Clustering.

I have a lot of annotation on my swift application. How can I only show the annotation on the map region currently visible?

I am trying to make an Uber clone in which there are a lot of annotation for passengers and driver. I want to only load and show the annotation which belong to the map region currently displayed, so that all the annotations are not added to the mapview and does not consume much memory.
I have two types of annotations: driver and passenger. I have different images for their annotation.
Here is how I am trying to do it:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? DriverAnnotation {
guard let dequeuedDriverAnnotation = mapView.dequeueReusableAnnotationView(withIdentifier: "driver") else {
let driverAnnotation = MKAnnotationView(annotation: annotation, reuseIdentifier: "driver")
driverAnnotation.image = UIImage(named: "driverAnnotation")
return driverAnnotation
}
dequeuedDriverAnnotation.annotation = annotation
return dequeuedDriverAnnotation
} else if let annotation = annotation as? PassengerAnnotation {
guard let dequeuedPassengerAnnotation = mapView.dequeueReusableAnnotationView(withIdentifier: "passenger") else {
let passengerAnnotation = MKAnnotationView(annotation: annotation, reuseIdentifier: "passenger")
passengerAnnotation.image = UIImage(named: "currentLocationAnnotation")
return passengerAnnotation
}
dequeuedPassengerAnnotation.annotation = annotation
return dequeuedPassengerAnnotation
}
return nil
}
}
How about defining function to check if annotation belong to the map region currently displayed, and try returning nil if it is out of region.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if !visibleMapRegion(annotation) {
return nil
}
...
To define this function see also:
How to check if MKCoordinateRegion contains CLLocationCoordinate2D without using MKMapView?
How do I determine if the current user location is inside of my MKCoordinateRegion?

set the callout annotation title from "DidSelectAnnotationView" in Swift 2.0 and Data From CoreData

please help. =) i have a problem to set the callout title. I receive the informations from core data with predicates.
When i click on a annotation on the map, i receive all data from the pin in my Logs.
how can set the title in the callout Bubble?
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView:MKPinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "CheckpointPins")
if annotation is MKUserLocation {
return nil }
else {
let pred:NSPredicate = NSPredicate(format: "checkpoint_longitude != nil && checkpoint_latitude != nil")
let items = fetchedResultsController?.fetchedObjects
let filt = (items! as NSArray).filteredArrayUsingPredicate(pred)
let startCheckPoint:Checkpoint = (filt as NSArray).firstObject as! Checkpoint!
let endCheckpoint:Checkpoint = (filt as NSArray).lastObject as! Checkpoint!
if filt.count != 0 {
if startCheckPoint .isEqual(annotation) {
annotationView.pinTintColor = MKPinAnnotationView.greenPinColor()
}else if endCheckpoint.isEqual(annotation) {
annotationView.pinTintColor = MKPinAnnotationView.purplePinColor()
}else {
annotationView.pinTintColor = MKPinAnnotationView.redPinColor()
}
}
let point = annotation as! Checkpoint
annotationView.tag = (point.checkpoint_order?.integerValue)!
annotationView.enabled = true
annotationView.animatesDrop = true
// annotationView.canShowCallout = true
annotationView.annotation = annotation
// annotationView.opaque = false
print("tag = \(annotationView.tag)")
}
return annotationView
}
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
if !(view.annotation!.isKindOfClass(MKUserLocation)) {
let pred:NSPredicate = NSPredicate(format: "checkpoint_longitude != nil && checkpoint_latitude != nil")
let pred2:NSPredicate = NSPredicate(format: "checkpoint_order == %d", view.tag)
let compoundPredicate:NSPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [pred, pred2])
let items = fetchedResultsController?.fetchedObjects
let filt = (items! as NSArray).filteredArrayUsingPredicate(compoundPredicate)
let point:Checkpoint = (filt as NSArray).firstObject as! Checkpoint
print(point.description)
}
}
}
First of all, if you want to show callouts, you need to set canShowCallout to true. There are no callouts unless you enable them.
In a comment you said that you're getting a crash with an error that reads must implement title when canShowCallout is YES on corresponding view <MKPinAnnotationView: 0x7fb9ef536e70... What this is telling you is that the object you used as your annotation does not implement the title part of the MKAnnotation protocol. The title is an optional part of the protocol unless you use callouts in which case you must implement it. The title is what's displayed on the callout. The error message is telling you that you enabled the callout but didn't provide a title to display. You can either add title to the class you're using as the MKAnnotation or you can skip using callouts.

How to change non selected annotation pin images on mapkit with Swift

I have a map and on this map, I have 10 custom annotation pins. All pins have the same custom image. When I click on a pin, I need to change all the other 9 annotation's images. it's possible to change the clicked pin's image but I need to keep as it is and I need to change all other pins images.
I tried to get all annotations with Map mapView.annotations and tried to find selected annotations and change other images but couldn't manage it. And idea how to do it?
Thanks in advance.
Conform to MKMapViewDelegate protocol and then:
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
let selectedAnnotation = view.annotation
for annotation in mapView.annotations {
if let annotation = annotation as? MKAnnotation where !annotation.isEqual(selectedAnnotation) {
// do some actions on non-selected annotations in 'annotation' var
}
}
Also you can save the selected annotation for later use here, if you want to process all annotations in another moment.
finally managed :) solved the problem little bit hard way but working smooth :) thank you for the tip rshev ;)
i used a bool for tap recognize
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is CustomAnnotation {
var pin = mapView.dequeueReusableAnnotationViewWithIdentifier(customAnnotationViewIdentifier)
pin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: customAnnotationViewIdentifier)
if tapControl {
pin.image = UIImage(named: "MapAnnotationIcon")
} else {
pin.image = UIImage(named: "SelectedMapAnnotationIcon")
}
if pin == nil {
pin.canShowCallout = false
} else {
pin.annotation = annotation
}
return pin
and when pin tapped ->
if let annotation = view.annotation as? CustomAnnotation {
tapControl = !tapControl
for annotation in mapView.annotations {
if let annotation = annotation as? MKAnnotation where !annotation.isEqual(selectedAnnotation) {
mapView.removeAnnotation(annotation)
}
}
addAnnotations()
println("tapped")
i removed all pins without selected pin, and then draw them back but this time tapcontrol is false so other pins are redrawed with another imageview, so thats what i exactly want to do.
You just have to overrride isSelected property inside your MKAnnotationView subclass.
override var isSelected: Bool {
didSet {
if isSelected {
// do stuff where annotation is selected
} else {
// do opposite
}
}
}