Stopping all animations in place on MKMapView Swift 3 iOS - ios

I have been stuck on this issue for the past day. I have created a custom MKAnnotation subclass to display various custom pins on an MKMapView. I recursively call a function that keeps animating these pins around the map. My goal is to stop all of these animations in place when the user taps on a button. I have tried
self.view.layer.removeAllAnimations()
and
self.map.layer.removeAllAnimations()
and other hacky solutions, but none seem to work.
Below is the code that creates the animation/pin movement
func animate(duration:Double, newLocation:CLLocationCoordinate2D){
UIView.animate(withDuration: duration, animations: {
self.coordinate = newLocation
}) { (done) in
self.finished_segment()
}
}
Any suggestions are much appreciated.

For anyone stuck on this issue. The problem was that I had to remove the animation from the MKAnnotationView associated with the annotation. I basically created a member variable in the custom class that I set in the mapView annotation delegate method as seen below.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "annotationView") ?? MKAnnotationView()
if let a = annotation as? Alien{
annotationView.image = a.image
a.annotationView = annotationView
}
return annotationView
}
And to remove the animation from the map. Simply call
self.annotationView.layer.removeAllAnimations()

Related

Guard let is executing, but the code after it still works?

Ok, so I have a MapKit app, and just finished setting up the MKAnnotationView stuff. My MKAnnotationView class looks like this:
class JumpSpotAnnotationView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
willSet {
// Extra safe, making sure there's no errors
guard (newValue as? JumpSpotAnnotation) != nil else {
print("The JumpSpotAnnotation or JumpSpotAnnotationView has something wrong if you are reading this. (JumpSpotAnnotationView)")
return
}
// Setting up UI for the little Callout bubble that appears when you tap the annotation to see more info
canShowCallout = true
calloutOffset = CGPoint(x: 0, y: 0)
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
//detailCalloutAccessoryView
markerTintColor = .blue
}
}
}
And my mapView viewFor function in my view controller looks like this:
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Make sure the annotation entering the screen is a JumpSpotAnnotation, and exists
guard let annotation = annotation as? JumpSpotAnnotation else {
print("The JumpSpotAnnotation or JumpSpotAnnotationView has something wrong if you are reading this. (mapView viewFor func)")
return nil
}
// Downcast the dequeued view as a JumpSpotAnnotationView, and make sure it has the same identifier as the registered JumpSpotAnnotationView above in viewDidLoad
let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: "JumpSpotAnnotation") as? JumpSpotAnnotationView
dequeuedView?.annotation = annotation
// Return annotation views here
return dequeuedView
}
}
Honestly, I put the guard let's in each of those, because I saw someone else do it that way and assumed it would be safer. I'm not really sure what their purpose is, other than making sure an annotation is actually entering the view, and is of the right type of annotation that I specified (I think it does that, at least).
Anyway, when I actually add the annotations by pressing a button in my app, everything works flawlessly, exactly as I want it to, but the print statements inside of the guard let's are showing up in the debugger. I have no idea what's causing them, nor any idea why my code is still working after they've triggered, when the fact that the guard let's have executed should stop the code below them from executing, and mess up my app. Can anyone offer ideas or explanations? I should add that the print statement from the mapView viewFor func appears once, as soon as the app loads up, then, the print statement from my JumpSpotAnnotationView appears each time I add an annotation.
I want to make sure I'm not missing some huge error that I'll regret down the line.
There are some MKAnnotations that are not the class of JumpSpotAnnotation when func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? works. It must be from MKMapView so dont worry about it. Your guard let works well.

Mapbox: Refreshing an annotationView when the annotation changes

I have custom annotations that sometimes display a textView above them.
They don't display a textView if a variable named text on my annotation is nil.
An annotation may have text to display, but the value of the text variable could change while the annotation is being displayed. In this case I would like the annotation to refresh so that it is no longer displaying the textView.
I already have a delegate function that either creates an annotation with a textView if the annotations text variable is set and creates an annotation without a textView if the text variable of the annotation is not set, it works something like this, although this is not the actual code
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView?{
if annotation is MyCustomAnnotation{
if annotation.hasText(){
return MyCustomAnnotationView(hasText: True)
}else{
return ViewWithoutTextView(hasText: False)
}
}
But if the annotation changes from having text to not having text or vice versa while the annotation is already being displayed, then I don't know how to refresh this or call this again so that the right annotation view is displayed
As #Magnas said in the comment, you would have to remove the annotation and re-add it to update the state.
It would be better to create one custom annotation view that has the logic to handle hiding/showing of the text view inside it. Then you just hold onto a reference of the annotation and update that through the annotationView without going through and messing with map annotations at all.
A rough example (lots of blanks to fill):
// your methods in your custom annotation. Use these wherever you want to change things
class CustomAnnotation: MGLAnnotationView {
func showText() { }
func hideText() { }
}
// Define data structure to access your annotation with some kind of key
dataSourceToAnnotationView: [String: CustomAnnotation]
// save your annotations so you can access them later
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "customReuseId")
if annotationView == nil {
annotationView = CustomAnnotation()
let key = "exampleKeyString"
dataSourceToAnnotationView[key] = annotationView as! CustomAnnotation
}
return annotationView
}

Showing MKAnnotation callout when clustered

I have a TableView that is used to show MapView annotation callouts when the cells are tapped.
In iOS 10 I can centre the MapView on an annotation then show it's callout using:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let location = locations[indexPath.item]
mapView.setCenter(location.coordinate, animated: true)
mapView.selectAnnotation(location, animated: true)
}
locations is an array of MKAnnotations. I'm using MKPinAnnotationViews on iOS 10 and MKMarkerAnnotationViews on iOS 11.
iOS 11 automatically hides and shows MKMarkerAnnotationViews as you zoom the map.
This has the unfortunate side effect of preventing .selectAnnotation() from working reliably because the marker could still be hidden after centering the map.
I've seen the docs and understand why:
If the specified annotation is not onscreen, and therefore does not
have an associated annotation view, this method has no effect.
Is there a way to disable annotation clustering/hiding?
Or some way to force the selected annotation to be visible?
You can set the displayPriority of an MKMarkerAnnotationView to a rawValue of 1000 and the less interesting MKMarkerAnnotationView's displayPriority to something lower. This will cause that marker annotation to take precedence over others.
In your case, you will like want to hold a reference to the annotation that you would like to select, remove that annotation from the map view and add it again. This will cause the map view to request a view for the annotation again and you can adjust the priority so that it is higher than the annotations around it. For example:
func showAnnotation()
{
self.specialAnnotation = annotations.last
self.mapView.removeAnnotation(self.specialAnnotation)
self.mapView.addAnnotation(self.specialAnnotation)
self.mapView.setCenter(self.specialAnnotation.coordinate, animated: true)
self.mapView.selectAnnotation(self.specialAnnotation, animated: true)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
let markerView = mapView.dequeueReusableAnnotationView(withIdentifier: "Marker", for: annotation) as? MKMarkerAnnotationView
let priority = (annotation as? Annotation) == self.specialAnnotation ? 1000 : 500
markerView?.displayPriority = MKFeatureDisplayPriority(rawValue: priority)
// optionally change the tint color for the selected annotation
markerView?.markerTintColor = priority == 1000 ? .blue : .red
return markerView
}
Where specialAnnotation is an object that conforms to MKAnnotation.

iOS Swift MapKit making an annotation draggable by the user?

How do I make it possible, using MapKit in Swift, for the user to drag an annotation from one position to another within the map? I have set the annotation view to be draggable, when my map view delegate creates the annotation view, like this:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var v : MKAnnotationView! = nil
if annotation is MyAnnotation {
let ident = "bike"
v = mapView.dequeueReusableAnnotationView(withIdentifier:ident)
if v == nil {
v = MyAnnotationView(annotation:annotation, reuseIdentifier:ident)
}
v.annotation = annotation
v.isDraggable = true
}
return v
}
The result is that the user can sort of drag the annotation - but only once. After that, the annotation becomes impossible to drag, and even worse, the annotation now no longer "belongs" to map - when the map is scrolled / panned, the annotation holds still rather than scrolling / panning with the map. What am I doing wrong?
It isn't enough to mark the annotation view by setting isDraggable to true. You must also implement mapView(_:annotationView:didChange:fromOldState:) in your map view delegate - and (even more important) this implementation must not be empty! Rather, your implementation must, at a minimum, communicate the drag state from the incoming parameters to the annotation view, like this:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch newState {
case .starting:
view.dragState = .dragging
case .ending, .canceling:
view.dragState = .none
default: break
}
}
Once you do that, the annotation will be properly draggable by the user.
(Many thanks to this answer for explaining this so clearly. I can't claim any credit! My answer here is merely a translation of that code into Swift.)

How to always show map view annotation callouts?

How do you always show the annotation callouts, i.e. don't hide them when you tab the map view?
Resetting the annotations also will bring the callout to view state true.
[mapView removeAnnotation: currentMarker];
[mapView addAnnotation:currentMarker];
The callout is shown when an MKAnnotationView is selected and the view's canShowCallout property is set to YES.
It is then hidden when that MKAnnotationView is deselected. This occurs by tapping another annotation view, or by tapping outside of the currently selected annotation view.
As the delegate of MKMapView (conforming to MKMapViewDelegate), you are told when an annotation view is selected and deselected, but it's too late to do anything about it.
If you want to not deselect an annotation view, you should subclass MKAnnotationView and override the setSelected:animated: method and stop the annotation view from being deselected.
Thanks, #Zumry Mohammed for this idea. This solution in swift works for me:
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
guard let ann = view.annotation else {return}
mapView.removeAnnotation(ann)
mapView.addAnnotation(ann)
mapView.selectAnnotation(ann, animated: false)
}
I just set isSelected property to true on viewFor annotation method and that is all.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let annotationV = MKAnnotationView(annotation: annotation, reuseIdentifier: nil)
annotationV.image = UIImage(named: "ZeusSurveyMarkerTaskIcon", in: Bundle(for: ZsurveysGeofenceLocationMapView.self), compatibleWith: nil)
annotationV.canShowCallout = true
annotationV.isSelected = true
return annotationV
}

Resources