views:

3840

answers:

4

I have an instance of MKMapView and would like to use custom annotation icons instead of the standard pin icons supplied by MKPinAnnotationView. So, I've setup a subclass of MKAnnotationView called CustomMapAnnotation and am overriding -(void)drawRect: to draw a CGImage. This works.

The trouble comes when I try to replicate the .animatesDrop functionality supplied by MKPinAnnotationView; I would love for my icons to appear gradually, dropped from above and in left-to-right order, when the annotations are added to the MKMapView instance.

Here is -(void)drawRect: for CustomMapAnnotation, which works when you just draw the UIImage (which is what the 2nd line does):

- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 [((Incident *)self.annotation).smallIcon drawInRect:rect];
 if (newAnnotation) {
  [self animateDrop];
  newAnnotation = NO;
 }
}

The trouble comes when you add the animateDrop method:

-(void)animateDrop {
 CGContextRef myContext = UIGraphicsGetCurrentContext();

 CGPoint finalPos = self.center;
 CGPoint startPos = CGPointMake(self.center.x, self.center.y-480.0);
 self.layer.position = startPos;

 CABasicAnimation *theAnimation;
 theAnimation=[CABasicAnimation animationWithKeyPath:@"position"];
 theAnimation.fromValue=[NSValue valueWithCGPoint:startPos];
 theAnimation.toValue=[NSValue valueWithCGPoint:finalPos];
 theAnimation.removedOnCompletion = NO;
 theAnimation.fillMode = kCAFillModeForwards;
 theAnimation.delegate = self;
 theAnimation.beginTime = 5.0 * (self.center.x/320.0);
 theAnimation.duration = 1.0;
 [self.layer addAnimation:theAnimation forKey:@""];
}

It just doesn't work, and there could be a lot of reasons why. I won't get into all of them now. The main thing I am wanting to know is if the approach is sound at all, or if I should try something entirely different.

I tried also to package up the whole thing into an animation transaction so that the beginTime parameter might actually work; this seemed to not do anything at all. I don't know if this is because I am missing some key point or whether it's because MapKit is trashing my animations somehow.

  // Does nothing
  [CATransaction begin];
  [map addAnnotations:list];
  [CATransaction commit];

If anyone has any experience with animated MKMapAnnotations like this, I'd love some hints, otherwise if you can offer CAAnimation advice on the approach, that'd be great too.

A: 
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

#import "placeDtl.h"



@interface AddressAnnotation : NSObject<MKAnnotation> {
    CLLocationCoordinate2D coordinate;
    NSString *mTitle;
    NSString *mSubTitle;
    NSString *Title;


}



@end

@interface MapView : 

UIViewController<MKMapViewDelegate,UIApplicationDelegate,CLLocationManagerDelegate> {

    IBOutlet MKMapView *mapView;
    IBOutlet UIButton *mybtn;



    AddressAnnotation *addAnnotation;

    CLLocationCoordinate2D location;



    AddressAnnotation *placemark2;
    AddressAnnotation *placemark3;
    AddressAnnotation *placemark4;
    AddressAnnotation *placemark5;
    AddressAnnotation *placemark6;


}



@end


==================================================================
==================================================================

#import "MapView.h"
#import <MapKit/MapKit.h>



@implementation AddressAnnotation

@synthesize coordinate;

- (NSString *)subtitle{
    return [NSString  stringWithFormat:@"%f",coordinate.latitude];
}
- (NSString *)title{
    return @"ww";
}


-(id)initWithCoordinate: (CLLocationCoordinate2D) c  
{
    coordinate=c;
    return self;

}



@end



@implementation MapView





/*
 // The designated initializer.  Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        // Custom initialization
    }
    return self;
}
*/


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    //[self showAddress];
    [super viewDidLoad];


    mapView.delegate=self;
    [self.view insertSubview:mapView atIndex:0];


    CLLocationManager *locationManager=[[CLLocationManager alloc] init];
    locationManager.delegate=self;
    locationManager.desiredAccuracy=kCLLocationAccuracyNearestTenMeters;
    [locationManager startUpdatingLocation];

    [NSTimer scheduledTimerWithTimeInterval:4 target:self selector:@selector(GetLocations) userInfo:nil repeats:NO];



}



- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
    location=newLocation.coordinate;
    //One location is obtained.. just zoom to that location

    MKCoordinateRegion region;
    region.center=location;
    //Set Zoom level using Span
    MKCoordinateSpan span;
    span.latitudeDelta=.0005;
    span.longitudeDelta=.0005;
    region.span=span;

    [mapView setRegion:region animated:TRUE];




}


-(void)GetLocations{


    CLLocationCoordinate2D myx;
    myx.longitude=location.longitude+0.0005;
    myx.latitude=location.latitude+0.0005;  
    placemark2=[[AddressAnnotation alloc] initWithCoordinate:myx];
    [mapView addAnnotation:placemark2];

    CLLocationCoordinate2D myx1;
    myx1.longitude=myx.longitude-0.0005;
    myx1.latitude=myx.latitude-0.0005;  
    placemark3=[[AddressAnnotation alloc] initWithCoordinate:myx1];
    [mapView addAnnotation:placemark3];

    CLLocationCoordinate2D myx2;
    myx2.longitude=myx1.longitude-0.005;
    myx2.latitude=myx1.latitude-0.005;  
    placemark4=[[AddressAnnotation alloc] initWithCoordinate:myx2];
    [mapView addAnnotation:placemark4];

    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(centerMapAroundAnnotations) userInfo:nil repeats:NO];
}

- (void)centerMapAroundAnnotations
{
    // if we have no annotations we can skip all of this
    if ( [[mapView annotations] count] == 0 )
        return;

    // then run through each annotation in the list to find the
    // minimum and maximum latitude and longitude values
    CLLocationCoordinate2D min;
    CLLocationCoordinate2D max; 
    BOOL minMaxInitialized = NO;
    NSUInteger numberOfValidAnnotations = 0;

    for ( id<MKAnnotation> a in [mapView annotations] )
    {
        // only use annotations that are of our own custom type
        // in the event that the user is browsing from a location far away
        // you can omit this if you want the user's location to be included in the region 
        if ( [a isKindOfClass: [AddressAnnotation class]] )
        {
            // if we haven't grabbed the first good value, do so now
            if ( !minMaxInitialized )
            {
                min = a.coordinate;
                max = a.coordinate;
                minMaxInitialized = YES;
            }
            else // otherwise compare with the current value
            {
                min.latitude = MIN( min.latitude, a.coordinate.latitude );
                min.longitude = MIN( min.longitude, a.coordinate.longitude );

                max.latitude = MAX( max.latitude, a.coordinate.latitude );
                max.longitude = MAX( max.longitude, a.coordinate.longitude );
            }
            ++numberOfValidAnnotations;
        }
    }


    if ( numberOfValidAnnotations == 0 )
        return;

    // Now that we have a min and max lat/lon create locations for the
    // three points in a right triangle
    CLLocation* locSouthWest = [[CLLocation alloc] 
                                initWithLatitude: min.latitude 
                                longitude: min.longitude];
    CLLocation* locSouthEast = [[CLLocation alloc] 
                                initWithLatitude: min.latitude 
                                longitude: max.longitude];
    CLLocation* locNorthEast = [[CLLocation alloc] 
                                initWithLatitude: max.latitude 
                                longitude: max.longitude];

    // Create a region centered at the midpoint of our hypotenuse
    CLLocationCoordinate2D regionCenter;
    regionCenter.latitude = (min.latitude + max.latitude) / 2.0;
    regionCenter.longitude = (min.longitude + max.longitude) / 2.0;
    // Use the locations that we just created to calculate the distance
    // between each of the points in meters.
    CLLocationDistance latMeters = [locSouthEast getDistanceFrom: locNorthEast];
    CLLocationDistance lonMeters = [locSouthEast getDistanceFrom: locSouthWest];

    MKCoordinateRegion region;
    region = MKCoordinateRegionMakeWithDistance( regionCenter, latMeters, lonMeters );

    MKCoordinateRegion fitRegion = [mapView regionThatFits: region];
    [mapView setRegion: fitRegion animated: YES];


}

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation{


    MKPinAnnotationView *annView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"currentloc"];
    annView.pinColor = MKPinAnnotationColorGreen;
    annView.animatesDrop=TRUE;
    annView.canShowCallout = YES;
    annView.calloutOffset = CGPointMake(-5, 5);

    mybtn = [UIButton buttonWithType:UIButtonTypeCustom];
    mybtn.frame = CGRectMake(0, 0, 35, 35);
    mybtn.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
    mybtn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;   
    [mybtn setImage:[UIImage imageNamed:@"Picture 1.png"] forState:UIControlStateNormal];
    annView.rightCalloutAccessoryView = mybtn;

    [mybtn addTarget:self action:@selector(mybtntabbed) forControlEvents:UIControlEventTouchUpInside];
    return annView;


}

Just use this code....

rkloves
+10  A: 

Hi,

First, you need to make your view controller implement MKMapViewDelegate if it doesn't already.

Then, implement this method:

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views { 
   MKAnnotationView *aV; 
   for (aV in views) {
     CGRect endFrame = aV.frame;

     aV.frame = CGRectMake(aV.frame.origin.x, aV.frame.origin.y - 230.0, aV.frame.size.width, aV.frame.size.height);

     [UIView beginAnimations:nil context:NULL];
     [UIView setAnimationDuration:0.45];
     [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
         [aV setFrame:endFrame];
     [UIView commitAnimations];

   }
}

Add your annotations to the MapView and when they are added, this delegate method will be called and will animate the pins from top to bottom as they are added.

The values for timing and positioning can be changed a little bit but I've tweaked it to make it look best/closest to the traditional drop (as far as I've tested). Hope this helps!

Paul Shapiro
Thank you~ thats working for me
ludo
Can u help me in my problem.. I have to show 4 lines of data in that bubble..Like One title..then description, date, location name.. All the example showing only 2 lines of details. i tried like appending wanted data in subtitle and seperated by a \n character.. But it didnt worked.. Plz help me
Sijo
Sijo, have a look at this.http://stackoverflow.com/questions/1350050/custom-mkannotationview-callout
Paul Shapiro
+2  A: 

Alternatively, if you're making a MKAnnotationView subclass, you can use didMoveToSuperview to trigger the animation. The following does a drop that ends in a slight 'squish' effect

  #define kDropCompressAmount 0.1

  @implementation MyAnnotationViewSubclass

  ...

  - (void)didMoveToSuperview {
      CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation.duration = 0.4;
      animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
      animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -400, 0)];
      animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];

      CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation2.duration = 0.10;
      animation2.beginTime = animation.duration;
      animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
      animation2.toValue = [NSValue valueWithCATransform3D:CATransform3DScale(CATransform3DMakeTranslation(0, self.layer.frame.size.height*kDropCompressAmount, 0), 1.0, 1.0-kDropCompressAmount, 1.0)];
      animation2.fillMode = kCAFillModeForwards;

      CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"transform"];
      animation3.duration = 0.15;
      animation3.beginTime = animation.duration+animation2.duration;
      animation3.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
      animation3.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
      animation3.fillMode = kCAFillModeForwards;

      CAAnimationGroup *group = [CAAnimationGroup animation];
      group.animations = [NSArray arrayWithObjects:animation, animation2, animation3, nil];
      group.duration = animation.duration+animation2.duration+animation3.duration;
      group.fillMode = kCAFillModeForwards;

      [self.layer addAnimation:group forKey:nil];
  }
Michael Tyson