I try to animate my custom MkAnnotation, it works great on iOS 5 but not on iOS 6. Here is my didAddAnnotationViews method :
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)annotationViews
{
NSTimeInterval delayInterval = 0;
for (MKAnnotationView *annView in annotationViews)
{
// Don't pin drop if annotation is user location
if ([annView.annotation isKindOfClass:[MKUserLocation class]]) {
continue;
}
// Check if current annotation is inside visible map rect, else go to next one
MKMapPoint point = MKMapPointForCoordinate(annView.annotation.coordinate);
if (!MKMapRectContainsPoint(self.mapView.visibleMapRect, point)) {
continue;
}
CGRect endFrame = annView.frame;
// Move annotation out of view
annView.frame = CGRectMake(annView.frame.origin.x, annView.frame.origin.y - self.view.frame.size.height, annView.frame.size.width, annView.frame.size.height);
[UIView animateWithDuration:1
delay:delayInterval
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
annView.frame = endFrame;
}
completion:^(BOOL finished){
if (isModal)
[self.mapView selectAnnotation:[[self.mapView annotations] objectAtIndex:0] animated:YES];
}];
delayInterval += 0.0625;
}
}
I made this method with some parts of code i found on internet. On iOS 5 the animation is perfect, but on iOS 6 pins are just appearing without any kind of animation. Setting the mapView delegate is the first thing I do on my viewDidLoad, and i've also tried to generate my annotations from viewDidAppear method , without success.
Any idea ?
Thanks.
EDIT : Solution found, I use the perform:withObject:afterDelay: method and it seems to work.
- (void)viewDidLoad
{
[super viewDidLoad];
mapView.delegate = self;
[self performSelector:#selector(addAnnotation) withObject:nil afterDelay:0.0];
}
- (void)addAnnotation
{
MapViewAnnotation *annotation = [[MapViewAnnotation alloc] initWithTitle:#"test" andCoordinate:CLLocationCoordinate2DMake(49.6, 6.2)];
[mapView addAnnotation:annotation];
}
I found a solution, look at the edit section.
Related
I've been dabbling with the new iOS 7 custom transition API and looked through all the tutorials/documentation I could find but I can't seem to figure this stuff out for my specific scenario.
So essentially what I'm trying to implement is a UIPanGestureRecognizer on a view where I would swipe up and transition to a VC whose view would slide up from the bottom while the current view would slide up as I drag my finger higher.
I have no problem accomplishing this without the interaction transition, but once I implement the interaction (the pan gesture) I can't seem to complete the transition.
Here's the relevant code from the VC that conforms to the UIViewControllerTransitionDelegate which is needed to vend the animator controllers:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Swipe"]) {
NSLog(#"PREPARE FOR SEGUE METHOD CALLED");
UIViewController *toVC = segue.destinationViewController;
[interactionController wireToViewController:toVC];
toVC.transitioningDelegate = self;
toVC.modalPresentationStyle = UIModalPresentationCustom;
}
}
#pragma mark UIViewControllerTransition Delegate Methods
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController: (UIViewController *)presented presentingController: (UIViewController *)presenting sourceController:(UIViewController *)source {
NSLog(#"PRESENTING ANIMATION CONTROLLER CALLED");
SwipeDownPresentationAnimationController *transitionController = [SwipeDownPresentationAnimationController new];
return transitionController;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
NSLog(#"DISMISS ANIMATION CONTROLLER CALLED");
DismissAnimatorViewController *transitionController = [DismissAnimatorViewController new];
return transitionController;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator {
NSLog(#"Interaction controller for dimiss method caled");
return interactionController.interactionInProgress ? interactionController:nil;
}
NOTE: The interaction swipe is only for the dismissal of the VC which is why it's in the interactionControllerForDismissal method
Here's the code for the animator of the dismissal which works fine when I tap on a button to dismiss it:
#import "DismissAnimatorViewController.h"
#implementation DismissAnimatorViewController
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return 1.0;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
NSTimeInterval duration = [self transitionDuration:transitionContext];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
CGRect initialFrameFromVC = [transitionContext initialFrameForViewController:fromVC];
UIView *containerView = [transitionContext containerView];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
NSLog(#"The screen bounds is :%#", NSStringFromCGRect(screenBounds));
toVC.view.frame = CGRectOffset(initialFrameFromVC, 0, screenBounds.size.height);
toVC.view.alpha = 0.2;
CGRect pushedPresentingFrame = CGRectOffset(initialFrameFromVC, 0, -screenBounds.size.height);
[containerView addSubview:toVC.view];
[UIView animateWithDuration:duration
delay:0
usingSpringWithDamping:0.6
initialSpringVelocity:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
fromVC.view.frame = pushedPresentingFrame;
fromVC.view.alpha = 0.2;
toVC.view.frame = initialFrameFromVC;
toVC.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
#end
Here's the code for the UIPercentDrivenInteractiveTransition subclass which serves as the interaction controller:
#import "SwipeInteractionController.h"
#implementation SwipeInteractionController {
BOOL _shouldCompleteTransition;
UIViewController *_viewController;
}
- (void)wireToViewController:(UIViewController *)viewController {
_viewController = viewController;
[self prepareGestureRecognizerInView:_viewController.view];
}
- (void)prepareGestureRecognizerInView:(UIView*)view {
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gesture.minimumNumberOfTouches = 1.0;
[view addGestureRecognizer:gesture];
}
- (CGFloat)completionSpeed {
return 1 - self.percentComplete;
NSLog(#"PERCENT COMPLETE:%f",self.percentComplete);
}
- (void)handleGesture:(UIPanGestureRecognizer*)gestureRecognizer {
// CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
// 1. Start an interactive transition!
self.interactionInProgress = YES;
[_viewController dismissViewControllerAnimated:YES completion:nil];
break;
case UIGestureRecognizerStateChanged: {
// 2. compute the current position
CGFloat fraction = fabsf(translation.y / 568);
NSLog(#"Fraction is %f",fraction);
fraction = fminf(fraction, 1.0);
fraction = fmaxf(fraction, 0.0);
// 3. should we complete?
_shouldCompleteTransition = (fraction > 0.23);
// 4. update the animation controller
[self updateInteractiveTransition:fraction];
NSLog(#"Percent complete:%f",self.percentComplete);
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
// 5. finish or cancel
NSLog(#"UI GESTURE RECOGNIZER STATE CANCELED");
self.interactionInProgress = NO;
if (!_shouldCompleteTransition || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
[self cancelInteractiveTransition];
NSLog(#"Interactive Transition is cancled.");
}
else {
NSLog(#"Interactive Transition is FINISHED");
[self finishInteractiveTransition];
}
break;
}
default:
NSLog(#"Default is being called");
break;
}
}
#end
Once again, when I run the code now and I don't swipe all the way to purposefully cancel the transition, I just get a flash and am presented with the view controller I want to swipe to. This happens regardless if the transition completes or is canceled.
However, when I dismiss via the button I get the transition specified in my animator view controller.
I can see a couple of issues here - although I cannot be certain that these will fix your problem!
Firstly, your animation controller's UIView animation completion block has the following:
[transitionContext completeTransition:YES];
Whereas it should return completion based on the result of the interaction controller as follows:
[transitionContext completeTransition:![transitionContext transitionWasCancelled]]
Also, I have found that if you tell the UIPercentDrivenInteractiveTransition that a transition is 100% complete, it does not call the animation controller completion block. As a workaround, I limit it to ~99.9%
https://github.com/ColinEberhardt/VCTransitionsLibrary/issues/4
I've created a number of example interaction and animation controllers here, that you might find useful:
https://github.com/ColinEberhardt/VCTransitionsLibrary
I had this same problem. I tried the fixes above and others, but nothing worked. Then I stumbled upon https://github.com/MrAlek/AWPercentDrivenInteractiveTransition, which fixed everything.
Once you add it to your project, just replace UIPercentDrivenInteractiveTransition with AWPercentDrivenInteractiveTransition.
Also, you have to set the animator before starting an interactive transition. In my case, I use the same class for UIViewControllerAnimatedTransitioning and UIViewControllerInteractiveTransitioning, so I just did it in init():
init() {
super.init()
self.animator = self
}
I'm currently adding annotations to my map through a loop... but the annotations are only appearing on my map in groups. Also, on load, only about 4 annotations are actually displayed on the map... but as I move the map a little, all of the annotations that should be there, suddenly appear.
How can I get all of the annotations to load in the right place, one at a time?
Thanks in advance!
Here is the code I'm using to add annotations:
NSString *incident;
for (incident in weekFeed) {
NSString *finalCoordinates = [[NSString alloc] initWithFormat:#"%#", [incident valueForKey:#"coordinates"]];
NSArray *coordinatesArray = [finalCoordinates componentsSeparatedByString:#","];
latcoord = (#"%#", [coordinatesArray objectAtIndex:0]);
longcoord = (#"%#", [coordinatesArray objectAtIndex:1]);
// Final Logs
NSLog(#"Coordinates in NSString: [%#] - [%#]", latcoord, longcoord);
CLLocationCoordinate2D coord;
coord.latitude = [latcoord doubleValue];
coord.longitude = [longcoord doubleValue];
DisplayMap *ann = [[DisplayMap alloc] init];
ann.title = [NSString stringWithFormat: #"%#", [incident valueForKey:#"incident_type"]];
ann.subtitle = [NSString stringWithFormat: #"%#", [incident valueForKey:#"note"]];
ann.coordinate = coord;
[mapView addAnnotation:ann];
[ann release];
}
// Custom Map Markers
-(MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil; //return nil to use default blue dot view
static NSString *AnnotationViewID = #"annotationViewID";
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil) {
annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
}
annotationView.canShowCallout = YES;
if ([annotationView.annotation.title isEqualToString:#"one"]) {
UIImage *pinImage = [UIImage imageNamed:#"marker_1.png"];
[annotationView setImage:pinImage];
}
if ([annotationView.annotation.title isEqualToString:#"two"]) {
UIImage *pinImage = [UIImage imageNamed:#"marker_2.png"];
[annotationView setImage:pinImage];
}
annotationView.annotation = annotation;
return annotationView;
}
- (void) mapView:(MKMapView *)mapV didAddAnnotationViews:(NSArray *)views {
CGRect visibleRect = [mapV annotationVisibleRect];
for (MKAnnotationView *view in views) {
CGRect endFrame = view.frame;
CGRect startFrame = endFrame; startFrame.origin.y = visibleRect.origin.y - startFrame.size.height;
view.frame = startFrame;
[UIView beginAnimations:#"drop" context:NULL];
[UIView setAnimationDuration:0.4];
view.frame = endFrame;
[UIView commitAnimations];
}
}
Adam,
This solution is a bit messy as I had to munge up one of my current projects to test, but hopefully this will work for you.
First an explanation, it's critical to separate data from UI presentation. The [MKMapView addAnnotation(s)] are just a data update to MKMapView and have no direct impact on animation or timing.
The delegate method mapView:didAddAnnotationViews: is where all of the custom presentation behavior should be defined. In your description you didn't want these to appear all at once, so you need to sequence your animations instead of performing them simultaneously.
One method is to add all of the annotations at once and then just add them with increasing animation delays, however new annotations that get added for whatever reason will begin their animations at zero again.
The method below sets up an animation queue self.pendingViewsForAnimation (NSMutableArray) to hold annotation views as they are added and then chains the animation sequentially.
I've replaced the frame animation with alpha to focus on the animation problem to separate it from the issue of some items not appearing. More on this after the code...
// Interface
// ...
// Add property or iVar for pendingViewsForAnimation; you must init/dealloc the array
#property (retain) NSMutableArray* pendingViewsForAnimation;
// Implementation
// ...
- (void)processPendingViewsForAnimation
{
static BOOL runningAnimations = NO;
// Nothing to animate, exit
if ([self.pendingViewsForAnimation count]==0) return;
// Already animating, exit
if (runningAnimations)
return;
// We're animating
runningAnimations = YES;
MKAnnotationView* view = [self.pendingViewsForAnimation lastObject];
[UIView animateWithDuration:0.4 animations:^(void) {
view.alpha = 1;
NSLog(#"Show Annotation[%d] %#",[self.pendingViewsForAnimation count],view);
} completion:^(BOOL finished) {
[self.pendingViewsForAnimation removeObject:view];
runningAnimations = NO;
[self processPendingViewsForAnimation];
}];
}
// This just demonstrates the animation logic, I've removed the "frame" animation for now
// to focus our attention on just the animation.
- (void) mapView:(MKMapView *)mapV didAddAnnotationViews:(NSArray *)views {
for (MKAnnotationView *view in views) {
view.alpha = 0;
[self.pendingViewsForAnimation addObject:view];
}
[self processPendingViewsForAnimation];
}
Regarding your second issue, items are not always appearing until you move the map. I don't see any obvious errors in your code, but here are some things I would do to isolate the problem:
Temporarily remove your mapView:didAddAnnotationViews:, mapView:annotationForView: and any other custom behaviors to see if default behavior works.
Verify that you have a valid Annotation at the addAnnotation: call and that the coordinates are visible (use [mapView visibleMapRect], MKMapRectContainsPoint(), and MKMapPointForCoordinate().
If it is still not functioning, look at where you are calling the add annotations code from. I try to avoid making annotation calls during map movement by using performSelector:withObject:afterDelay. You can precede this with an [NSObject cancelPreviousPerformRequestsWithTarget:selector:object:] to create a slight delay prior to loading annotations in case the map is being moved a long distance with multiple swipes.
One last point, to achieve the pin-drop effect you're looking for, you probably want to offset by a fixed distance from the original object instead of depending on annotationVisibleRect. Your current implementation will result in pins moving at different speeds depending on their distance from the edge. Items at the top will slowly move into place while items at the bottom will fly rapidly into place. Apple's default animation always drops from the same height. An example is here: How can I create a custom "pin-drop" animation using MKAnnotationView?
Hope this helps.
Update:
To demonstrate this code in action I've attached a link to a modified version of Apple's Seismic demo with the following changes:
Changed Earthquake.h/m to be an MKAnnotation object
Added SeismicMapViewController.h/m with above code
Updated RootViewController.h/m to open the map view as a modal page
See: http://dl.dropbox.com/u/36171337/SeismicXMLWithMapDelay.zip
You will need to pause updating after you add each annotation and allow the map to have time to refresh.
I have created a custom MKAnnotationView for User Location:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
if (navStatus == NavStatusHeadingEnabled) {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
locationView = [[CustomLocationView alloc] initWithAnnotation:annotation reuseIdentifier:#"locationIdentifier"];
return locationView;
}
}
return nil;
}
CustomLocationView.h
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self != nil)
{
self.backgroundColor = [UIColor clearColor];
blueDot = [UIImage imageNamed:#"userLocationDot.png"].CGImage;
CGImageRetain(blueDot);
CGPoint blueDotCenter = CGPointMake((self.frame.size.width - (CGImageGetWidth(blueDot) / 2)) / 2, (self.frame.size.height - (CGImageGetHeight(blueDot) / 2)) / 2);
blueDotLayer = [CALayer layer];
blueDotLayer.frame = CGRectMake(blueDotCenter.x, blueDotCenter.y , CGImageGetWidth(blueDot) / 2, CGImageGetHeight(blueDot) / 2);
blueDotLayer.contents = (id)blueDot;
blueDotLayer.shadowOpacity = 0.4;
blueDotLayer.shadowColor = [UIColor blackColor].CGColor;
blueDotLayer.shadowOffset = CGSizeMake(0.4, 0.3);
blueDotLayer.shadowRadius = 1.0f;
[self.layer insertSublayer:blueDotLayer above:self.layer];
}
return self;
}
- (void)setAnnotation:(id <MKAnnotation>)annotation
{
[super setAnnotation:annotation];
[self setNeedsDisplay];
}
- (void)dealloc {
[blueDotLayer release];
[super dealloc];
}
The problem is it just stays on the same place and not moving like the blue dot.
What I am doing wrong?
Thanks
Bill.
I ran into this problem just now as well. I'm not sure if this is expected behavior or not, but for whatever reason it is up to us to move our custom MKUserLocation annotation views.
A naive solution is
- (void) locationController: (LocationController *) locationController
didUpdateToLocation: (CLLocation *) location
{
[[self mapView] setShowsUserLocation:NO];
[[self mapView] setShowsUserLocation:YES];
}
But this makes the current location annotation jump around the screen which I found undesirable.
Better yet is to keep a reference to the custom annotation view as an ivar in your view controller and then do:
- (void) locationController: (LocationController *) locationController
didUpdateToLocation: (CLLocation *) location
{
CGPoint newCenterPoint = [[self mapView] convertCoordinate:[location coordinate] toPointToView:[[self customAnnotationView] superview]];
newCenterPoint.x += [[self customAnnotationView] centerOffset].x;
newCenterPoint.y += [[self customAnnotationView] centerOffset].y;
[UIView animateWithDuration:0.3f animations:^{
[[self customAnnotationView] setCenter:newCenterPoint];
}];
}
This is good except when you change the zoom level the annotation stays where it was relative to the map view's rect and then animates to the correct location only after the zoom or pan is complete. Best to follow Apple's lead and make the current location annotation disappear and reappear during region changes:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
[[self mapView] setShowsUserLocation:NO];
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
[[self mapView] setShowsUserLocation:YES];
}
Bill,
You need a CLLocationManager that has been initialized with an applicable desiredAccuracy and distanceFilter and then implement the applicable delegate methods, and set your own code as the delegate on the locationManager instance.
At a minimum you should have the following method which the Location Manager will call once it has determined the current location with in the desiredAccuracy and then again whenever the distanceFilter has been met.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation ;
Cheers,
Mack
I just found an answer for this problem. My code is in Monotouch, but it should be easy to re-do the same in ObjC.
to show customized image over default MKUserLocation we need to add a subview on top of the original one. to do this, override DidAddAnnotationViews in MKMapView delegate
void DidAddAnnotationViews (object sender, MKMapViewAnnotationEventArgs e)
{
MKAnnotationView v = mapView.ViewForAnnotation(mapView.UserLocation);
if(v != null)
{
if(v.Subviews.Count() == 0)
{
UIImageView iv = new UIImageView(new RectangleF(0,0, 22, 22));
iv.Image = UIImage.FromFile("res/pins/Yhere.png");
v.AddSubview(iv);
v.BringSubviewToFront(iv);
}
}
}
This gives custom image moving on top of blue dot. more over, user tracking and location updates features works perfectly and you still can see blue circles that indicate location accuracy.
Have fun customizing MKUserLocation!
Here is the answer from Apple Developer Technical Support
I just finished talking to the MapKit engineers and they confirmed that this is a bug in iOS.
My test app is experiencing the same problem.
I don't know if this was fixed in iOS 6, the answer is for the iOS 5.
HI,
I have a view that has three UIScrollViews on the screen. I want to randomly scroll the UIScrollViews to different positions whenever a user shakes the iPhone, but I am unable to get it done.
I have implemented the following in my ViewController class to detect and handle the shake gesture, the 3 UIScrollViews also belong to the same class. The shake gesture is detected, but the UIScrollViews do not change. What am I doing Wrong??
i have tried both motionBegan and motionEnded.
-(BOOL)canBecomeFirstResponder {
return YES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
int randomTag = arc4random() % [dirContents count];
CGRect nextImageView = [[scrollView1 viewWithTag:2] frame];
[scrollView1 scrollRectToVisible:nextImageView animated:YES];
randomTag = arc4random() % [dirContents count];
nextImageView = [[scrollView2 viewWithTag:4] frame];
[scrollView2 scrollRectToVisible:nextImageView animated:YES];
randomTag = arc4random() % [dirContents count];
nextImageView = [[scrollView3 viewWithTag:4] frame];
[scrollView3 scrollRectToVisible:nextImageView animated:YES];
NSLog(#"Shake Detected End");
}
}
Thank You
Have you tried using SetContentOffset instead of scrollRectToVisible yet?
if the images in your tableView are of equal height the offset per "Element" is always the same.
[scrollView3 setContentOffset:yourRandomOffsetInPixels animated:YES];
maybe this works.
also consider, that The Problem might be that your shake-detection Method runs on a separate Thread this would mean that you have to call your motionEnded Method on the mainthread like so:
[self performSelectorOnMainThread:#selector(motionEnded) withObject:nil waitUntilDone:NO];
Did you check your nextImageView variable to see if it was correct ?
Further more if you are trying will have the motion of a slot machine, I would recommend you to use UITableView instead of doing it by yourself with scrollView
Just one quick question. In your example code, you generate a random tag:
randomTag = arc4random() % [dirContents count];
but then you use a specific tag value (4 in this case)? I assume it still doesn't work when you use the randomTag value? and that you were just doing some testing?
nextImageView = [[scrollView2 viewWithTag:4] frame];
This is about an iPhone App using MKMapKit:
I created a custom MKAnnotationView for a draggable Annotation. I want to create a custom animation. I set a custom pin image and the annotation is draggable (which both is not shown here, it happens in the mapview) with the following code:
- (void) movePinUpFinished {
[super setDragState:MKAnnotationViewDragStateDragging];
[self setDragState:MKAnnotationViewDragStateDragging];
}
- (void) setDragState:(MKAnnotationViewDragState) myState {
if (myState == MKAnnotationViewDragStateStarting) {
NSLog(#"starting");
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-20);
self.center = endPoint;
[self movePinUpFinished];
}
if (myState == MKAnnotationViewDragStateEnding) {
NSLog(#"ending");
[super setDragState:MKAnnotationViewDragStateEnding];
[self setDragState:MKAnnotationViewDragStateNone];
[super setDragState:MKAnnotationViewDragStateNone];
}
if (myState == MKAnnotationViewDragStateDragging) {
NSLog(#"dragging");
}
if (myState == MKAnnotationViewDragStateCanceling) {
NSLog(#"cancel");
}
if (myState == MKAnnotationViewDragStateNone) {
NSLog(#"none");
}
}
Everything works fine, the annotation is moved up a bit, is draggable and when i release the annotation, the mapview receives the "dragstateending".
But now I want the animation to run over a time period and change the dragStateStarting to the following:
if (myState == MKAnnotationViewDragStateStarting) {
NSLog(#"starting");
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-20);
[UIView animateWithDuration:1.0
animations:^{ self.center = endPoint; }
completion:^(BOOL finished){ [self movePinUpFinished]; }];
}
The animations runs as wanted over the period of a second and the annotation is draggable. But when I release the annotation, the mapview is not receiving the ending through the delegat. What I also recognized was that when I am doing the animation with "UIView animateWithDuration..." is that immedently after beginning the dragging, as the animation starts, the ballon of the annotation opens. When i am setting the new center without the animation, the balloon keeps closed and is only opened after finishing the dragging by releasing the annotation.
What am I doing wrong? Is this the right way to override setDragState. Do I really have to call the super class? But without setting the dragstate in the superclass my mapview didnt realized the changes of the dragstate.
I wonder about the original implementation of MKPinAnnotationView, but because it is an internal Class I couldn't find a description of the setDragState method.
Thx for help. Cheers,
Ben
I had the pin drag working but was trying to figure out why the pin annimations that occur when you don't override setDragState - no longer work in my implementation. Your question contained my answer .. Thanks!
Part of the problem with your code is that once you override the setDragState function, per the xcode documentation, you are responsible for updating the dragState variable based on the new state coming in. I would also be a little concerned about your code calling itself (setDragState calling [self setDragState]).
Here is the code I ended up (with your help) that does all of the lifts, drags and drops as I expect them to occur. Hope this helps you too!
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
if (newDragState == MKAnnotationViewDragStateStarting)
{
// lift the pin and set the state to dragging
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ self.dragState = MKAnnotationViewDragStateDragging; }];
}
else if (newDragState == MKAnnotationViewDragStateEnding)
{
// save the new location, drop the pin, and set state to none
/* my app specific code to save the new position
objectObservations[ACTIVE].latitude = pinAnnotation.coordinate.latitude;
objectObservations[ACTIVE].longitude = pinAnnotation.coordinate.longitude;
posChanged = TRUE;
*/
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ self.dragState = MKAnnotationViewDragStateNone; }];
}
else if (newDragState == MKAnnotationViewDragStateCanceling)
{
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ self.dragState = MKAnnotationViewDragStateNone; }];
}
}
While Brian's solution worked, it lacked taking into account the users finger blocking the annotation view which is being manipulated.
This means that the user could not precisely place the pin once he was dragging it. The standard MKPinAnnotationView does a great job at this, what happens is when the finger begins dragging, the pin is lifted above the finger, and the visual point of the pin is used for placement not the previous centre point which now resides under the finger.
In addition to this my implementation also adds another animation when dropping the pin after dragging, by lifting the pin and dropping it with a higher speed. This is very close the the native user experience and will be apreciated by your users.
Please check out my gist on GitHub for the code.
What's really cool is setting a delegate is optional, optionally a notification is sent when the annotation view is dropped back onto the map.
I didn't study Ben's code much but it didn't worked for me. So I tried Brian's and it works great. Thanks a lot! I've been trying to solve annotation's animation during drag'n'drop for a long time.
But I have one suggestion to Brian's solution. I think that it would be better to support MKMapKit's delegate and notify it about changing dragState and save new position within standard delegate's method: - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState. Here's my code:
DraggableAnnotationView.h:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface DraggableAnnotationView : MKAnnotationView
{
id <MKMapViewDelegate> delegate;
MKAnnotationViewDragState dragState;
}
#property (nonatomic, assign) id <MKMapViewDelegate> delegate;
#property (nonatomic, assign) MKAnnotationViewDragState dragState;
#property (nonatomic, assign) MKMapView *mapView;
#end
DraggableAnnotationView.m:
#import "DraggableAnnotationView.h"
#import "MapAnnotation.h"
#implementation DraggableAnnotationView
#synthesize delegate, dragState, mapView;
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
[delegate mapView:mapView annotationView:self didChangeDragState:newDragState fromOldState:dragState];
if (newDragState == MKAnnotationViewDragStateStarting) {
// lift the pin and set the state to dragging
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ dragState = MKAnnotationViewDragStateDragging; }];
} else if (newDragState == MKAnnotationViewDragStateEnding) {
// drop the pin, and set state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ dragState = MKAnnotationViewDragStateNone; }];
} else if (newDragState == MKAnnotationViewDragStateCanceling) {
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+20);
[UIView animateWithDuration:0.2
animations:^{ self.center = endPoint; }
completion:^(BOOL finished)
{ dragState = MKAnnotationViewDragStateNone; }];
}
}
- (void)dealloc
{
[super dealloc];
}
#end