Reputation: 73
I'm using the custom MKAnnotationView animations provided by Daniel here: Subclassing MKAnnotationView and overriding setDragState but I run into an issue.
After the pin drop animation, when I go to move the map the mkannotationview jumps back to its previous location before the final pin drop animation block is called.
It seems to me that dragState=MKAnnotationViewDragStateEnding is being called before the animation runs? How can I get around this issue and set the final point of the mkannotationview to be the point it's at when the animation ends?
#import "MapPin.h"
NSString *const DPAnnotationViewDidFinishDrag = @"DPAnnotationViewDidFinishDrag";
NSString *const DPAnnotationViewKey = @"DPAnnotationView";
// Estimate a finger size
// This is the amount of pixels I consider
// that the finger will block when the user
// is dragging the pin.
// We will use this to lift the pin even higher during dragging
#define kFingerSize 20.0
@interface MapPin()
@property (nonatomic) CGPoint fingerPoint;
@end
@implementation MapPin
@synthesize dragState, fingerPoint, mapView;
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
if(mapView){
id<MKMapViewDelegate> mapDelegate = (id<MKMapViewDelegate>)mapView.delegate;
[mapDelegate mapView:mapView annotationView:self didChangeDragState:newDragState fromOldState:dragState];
}
// Calculate how much to life the pin, so that it's over the finger, no under.
CGFloat liftValue = -(fingerPoint.y - self.frame.size.height - kFingerSize);
if (newDragState == MKAnnotationViewDragStateStarting)
{
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[MapPin animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateDragging;
}];
}
else if (newDragState == MKAnnotationViewDragStateEnding)
{
// lift the pin again, and drop it to current placement with faster animation.
__block CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[MapPin animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[MapPin animateWithDuration:0.1
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
if(!mapView)
[[NSNotificationCenter defaultCenter] postNotificationName:DPAnnotationViewDidFinishDrag object:nil userInfo:[NSDictionary dictionaryWithObject:self.annotation forKey:DPAnnotationViewKey]];
}];
}];
}
else if (newDragState == MKAnnotationViewDragStateCanceling)
{
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
}];
}
}
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
// When the user touches the view, we need his point so we can calculate by how
// much we should life the annotation, this is so that we don't hide any part of
// the pin when the finger is down.
fingerPoint = point;
return [super hitTest:point withEvent:event];
}
@end
Upvotes: 1
Views: 1467
Reputation: 86
I had the same problem, especially under iOS 8. After many hours of testing I believe that iOS keeps track of where it thinks self.center
of the annotation is during the time that the state is MKAnnotationViewDragStateDragging
. You need to use extreme caution if you animate self.center
when handling MKAnnotationViewDragStateEnding
. Read that as "I couldn't get that to work, ever."
Instead, I kept Daniel's original code when handling states MKAnnotationViewDragStateStarting
and MKAnnotationViewDragStateCanceling
, I animated self.center
. When handling MKAnnotationViewDragStateEnding
I animated self.transform
instead of self.center
. This maintains the actual location of the annotation and just changes how it is rendered.
This works well for me running either iOS 7.1 and iOS 8.0. Also fixed a bug in hitTest
, and added some code to reselect the annotation after dragging or canceling. I think that is the default behavior of MKPinAnnotationView
.
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
if(mapView){
id<MKMapViewDelegate> mapDelegate = (id<MKMapViewDelegate>)mapView.delegate;
[mapDelegate mapView:mapView annotationView:self didChangeDragState:newDragState fromOldState:dragState];
}
// Calculate how much to lift the pin, so that it's over the finger, not under.
CGFloat liftValue = -(fingerPoint.y - self.frame.size.height - kFingerSize);
if (newDragState == MKAnnotationViewDragStateStarting)
{
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateDragging;
}];
}
else if (newDragState == MKAnnotationViewDragStateEnding)
{
CGAffineTransform theTransform = CGAffineTransformMakeTranslation(0, -liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.transform = theTransform;
}
completion:^(BOOL finished){
CGAffineTransform theTransform2 = CGAffineTransformMakeTranslation(0, 0);
[UIView animateWithDuration:0.2
animations:^{
self.transform = theTransform2;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
if(!mapView)
[[NSNotificationCenter defaultCenter] postNotificationName:DPAnnotationViewDidFinishDrag object:nil userInfo:[NSDictionary dictionaryWithObject:self.annotation forKey:DPAnnotationViewKey]];
// Added this to select the annotation after dragging.
// This is the behavior for MKPinAnnotationView
if (mapView)
[mapView selectAnnotation:self.annotation animated:YES];
}];
}];
}
else if (newDragState == MKAnnotationViewDragStateCanceling)
{
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
// Added this to select the annotation after canceling.
// This is the behavior for MKPinAnnotationView
if (mapView)
[mapView selectAnnotation:self.annotation animated:YES];
}];
}
}
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
// When the user touches the view, we need his point so we can calculate by how
// much we should life the annotation, this is so that we don't hide any part of
// the pin when the finger is down.
// Fixed a bug here. If a touch happened while the annotation view was being dragged
// then it screwed up the animation when the annotation was dropped.
if (dragState == MKAnnotationViewDragStateNone)
{
fingerPoint = point;
}
return [super hitTest:point withEvent:event];
}
Upvotes: 7