I have around 500 pins I would like to display on a MKMapView.
However, when I call mapView.addAnnotations(places) (places being an array of MKAnnotation objects), the pins slowly drop one by one.
I would like to either :
drop all pins at once at load
cancel the drop animation altogether
Is this possible ?
You should set animatesDrop property of your MKAnnotationViews to NO(false)
By setting animation property to No will drop all the Pins at once on the MapView.
MKAnnotationView *annotationView =[[MKAnnotationView alloc]init];
annotationView.animatesDrop=FALSE;
You'll need to implement your own drop animation in the didAddAnnotationViews delegate method. You should also set animatesDrop to NO to avoid a possible double animation.
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)annotationViews
{
NSTimeInterval delayInterval = 0;
for (MKAnnotationView *annView in annotationViews)
{
CGRect endFrame = annView.frame;
annView.frame = CGRectOffset(endFrame, 0, -500);
[UIView animateWithDuration:0.125
delay:delayInterval
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ annView.frame = endFrame; }
completion:NULL];
delayInterval += 0.0625;
}
}
Related
I am working in a map application which renders a polyline over a map. I am having an issue when zooming, it keeps the previous polyline on the tile.
I have tried to force redrawing:
[self.mapView reloadInputViews];
[self.mapView.layer setNeedsDisplay];
[self.mapView setNeedsDisplay];
Also I tried to slow the zoom speed, but the issue is still in there:
[MKMapView animateWithDuration:2
delay:0
usingSpringWithDamping:0.6
initialSpringVelocity:10
options:UIViewAnimationOptionCurveEaseOut
animations:^{
[self.mapView setVisibleMapRect:unionRectThatFits
edgePadding:UIEdgeInsetsMake(20, 10, 20, 10)
animated:YES];
}
completion: nil];
Does anyone know about this?
I found the resolution to the problem here: How to refresh an MKOverlayRenderer when mapView change
So I added:
override var boundingMapRect: MKMapRect {
return MKMapRectWorld
}
To my MKPolyline & MKCircle subclass.
I have a problem with my function.
[UIView animateWithDuration:5 animations:^{
//set end coordinates for marker = MKAnnotationPoint
[self.followDriverMarker setCoordinate:CLLocationCoordinate2DMake(latitde, longitude)];
//set end coordinate for camera/map
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(latitde, longitude) animated:NO];
}
completion:^(BOOL finished) {
if (finished) {
if (self.currentPosition < [self.followDriverList count] - 2) {
self.currentPosition++;
//start next one
[self runAnimation];
} else {
//animation is finished
//TODO
self.isAnimationRunning = NO;
}
}
}];
The function will look in a List if there are locations left. If so it will run again. That works. The only problem is. If the animation is running. There is no interaction possible with the mapView. The other problem is that i cannot find how to cancel,stop or remove my Animation.
If I put:
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(latitde, longitude) animated:NO];
inside the the completionBlock, I have interaction with map. But I don't want to do that because I want to animate it the same time. Also here I can't find a way to cancel the animation.
Please don't say removeAllAnimation. This is not working.
I believe this answers your question.
Basically you commit a new animation on the same target with a short duration. By setting the setAnimationBeginsFromCurrentState flag you prevent weird jumps.
I am trying to center an MKMapView after an annotation was selected. I also have enabled canShowCallout but it seems that iOS is first displaying the callout (which is shifted when it would not fit in the screen) and then the map is being moved, resulting in the callout being not completely visible on the screen.
How can I center the map BEFORE the callout's position is being rendered and displayed?
I wanted to accomplish the same thing and ended up doing the following.
A word of caution before I begin: I know the solution is pretty ugly!...but hey, it works.
Note: I am targeting iOS 9 but it should work on prior versions of iOS:
Okay, here we go:
first off, create a new property in your view controller, e.g.: #property(nonatomic, assign, getter=isPinCenteringOngoing) BOOL pinCenteringOngoing;
in mapView:viewForAnnotation: set canShowCallout to NO for your annotationViews
in mapView:didSelectAnnotationView: do the following:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
if([view isKindOfClass:$YOURANNOTATIONVIEWCLASS$.class])
{
if(!self.isPinCenteringOngoing)
{
self.pinCenteringOngoing = YES;
[self centerMapOnSelectedAnnotationView:($YOURANNOTATIONVIEWCLASS$ *)view];
}
else
{
self.pinCenteringOngoing = NO;
}
}
}
in mapView:didDeselectAnnotationView: do the following:
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
if([view isKindOfClass:$YOURANNOTATIONVIEWCLASS$.class])
{
if(!self.isPinCenteringOngoing)
{
view.canShowCallout = NO;
}
}
}
and finally create a new method that does the actual work:
- (void)centerMapOnSelectedAnnotationView:($YOURANNOTATIONVIEWCLASS$ *)view
{
// Center map
CGPoint annotationCenter = CGPointMake(CGRectGetMidX(view.frame), CGRectGetMidY(view.frame));
CLLocationCoordinate2D newCenter = [self.mapView convertPoint:annotationCenter toCoordinateFromView:view.superview];
[self.mapView setCenterCoordinate:newCenter animated:YES];
// Allow callout to be shown
view.canShowCallout = YES;
// Deselect and then select the annotation so the callout is actually displayed
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void)
{
[self.mapView deselectAnnotation:view.annotation animated:NO];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void)
{
[self.mapView selectAnnotation:view.annotation animated:NO];
});
});
}
To complete my answer, here is a textual explanation of what I'm doing in the code above and why I'm doing it:
What I want is the annotation to be centered on screen, and the callout to be centered above it.
What I get by default is:
When selecting an annotation, the map opens the callout, and if necessary adjusts the map so the callout fits on screen. By no mean does that standard implementation guarantee, that the callout is "centered" above the annotation.
By centering the map with setCenterCoordinate:, the annotation view is centered on the map.
Now the two previous points combined can result in the callout to be "cut off" as the annotation is centered on the map, but the callout is not centered above the annotation.
To fix this, I do the following:
first I disable the callout to be displayed by default, setting canShowCallout to NO for every annotationView
when the user selects an annotation, I first center the map
I then allow the callout to be shown, setting canShowCallout to YES for the selected annotation
I then deselect and then again select the annotation, so the callout is actually displayed
in order for the callout to be correctly centered above the annotation, I need to do the deselecting/selecting somewhat delayed so that the map centering can complete
I hope my answer may prove useful.
Here an other solution :
Create a new boolean property var selectFirstAnnotation = false in your controller
Set it to true before to center the annotation
Add this is in regionDidChangeAnimated.
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if selectFirstAnnotation == true {
if let annotation = mapView.annotations.first(where: { !($0 is MKUserLocation) }) {
mapView.selectAnnotation(annotation, animated: true)
selectFirstAnnotation = false
}}}
Works fine for my behaviour
I tried both previous solutions and Greg's is the correct answer with a couple of tweaks... I put the map centering in and animation block to slow down the animation.
UIView.animate(withDuration: 0.8) {
self.mapView.setCenter(CLLocationCoordinate2D(latitude: newCenter.latitude, longitude: newCenter.longitude), animated: true)
}
Then I was getting an unacceptable blip from the separation of the deselect and select calls into different dispatches with different times and discovered they can both go in the same dispatch. Adding animated: true to the select call adds a nice touch as well.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
mapView.deselectAnnotation(view.annotation, animated: false)
mapView.selectAnnotation(view.annotation!, animated: true)
}
I'm adding and removing annotations to a map view. When I remove one it disappears abruptly and looks a bit startling, I'd rather it faded away gracefully.
I tried removing it with UIView:animateWithDuration: but it wasn't an animatable attribute.
If there's no other easy solution I was thinking I could get the annotation view to fade its alpha and then remove its annotation from the map. However the problem with this is it doesn't seem like an annotation view has a reference to its map view? Adding one starts to get a bit messy.
Is there some easy quick solution to removing an annotation gracefully?
Using animateWithDuration should work fine. To fade the removal of an annotation, one can:
MKAnnotationView *view = [self.mapView viewForAnnotation:annotation];
if (view) {
[UIView animateWithDuration:0.5 delay:0.0 options:0 animations:^{
view.alpha = 0.0;
} completion:^(BOOL finished) {
[self.mapView removeAnnotation:annotation];
view.alpha = 1.0; // remember to set alpha back to 1.0 because annotation view can be reused later
}];
} else {
[self.mapView removeAnnotation:annotation];
}
I think your proposed solution is the correct one. Set up an animation to opacity = 0, then, upon completion, remove the annotation from the MKMapView. The code that starts the animation doesn't have to be in the view itself; a better location for the code may be the view controller. Consider using NSNotificationCenter to notify the view controller that an annotation is requesting fade-out-and-remove.
I have something like 30 annotations in my map and I want speed up the dropping animation.
Is it possible speed up the drop of annotation in MKMapView or drop all of them at once?
You'll need to implement your own drop animation in the didAddAnnotationViews delegate method. You should also set animatesDrop to NO to avoid a possible double animation.
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)annotationViews
{
NSTimeInterval delayInterval = 0;
for (MKAnnotationView *annView in annotationViews)
{
CGRect endFrame = annView.frame;
annView.frame = CGRectOffset(endFrame, 0, -500);
[UIView animateWithDuration:0.125
delay:delayInterval
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ annView.frame = endFrame; }
completion:NULL];
delayInterval += 0.0625;
}
}
This drops the annotations at a rate you specify.
To drop them all at once, hard-code the delay parameter to 0.0 instead of the incrementing delayInterval.