Scaling MKMapView Annotations relative to the zoom level
Asked Answered
L

1

14

The Problem I'm trying to create a visual radius circle around a annonation, that remains at a fixed size in real terms. Eg. So If i set the radius to 100m, as you zoom out of the Map view the radius circle gets progressively smaller.

I've been able to achieve the scaling, however the radius rect/circle seems to "Jitter" away from the Pin Placemark as the user manipulates the view.

I'm lead to believe this is much easier to achieve on the forthcoming iPhone OS 4, however my application needs to support 3.0.

The Manifestation Here is a video of the behaviour.

The Implementation The annotations are added to the Mapview in the usual fashion, and i've used the delegate method on my UIViewController Subclass (MapViewController) to see when the region changes.

-(void)mapView:(MKMapView *)pMapView regionDidChangeAnimated:(BOOL)animated{

//Get the map view
MKCoordinateRegion region;
CGRect rect;

//Scale the annotations
for( id<MKAnnotation> annotation in [[self mapView] annotations] ){

    if( [annotation isKindOfClass: [Location class]] && [annotation conformsToProtocol:@protocol(MKAnnotation)] ){
        //Approximately 200 m radius
        region.span.latitudeDelta = 0.002f;
        region.span.longitudeDelta = 0.002f;

        region.center = [annotation coordinate];

        rect = [[self mapView] convertRegion:region toRectToView: self.mapView];

        if( [[[self mapView] viewForAnnotation: annotation] respondsToSelector:@selector(setRadiusFrame:)] ){

            [[[self mapView] viewForAnnotation: annotation] setRadiusFrame:rect];

        }

    }

}

The Annotation object (LocationAnnotationView)is a subclass of the MKAnnotationView and it's setRadiusFrame looks like this

-(void) setRadiusFrame:(CGRect) rect{

CGPoint centerPoint;

//Invert
centerPoint.x = (rect.size.width/2) * -1;
centerPoint.y = 0 + 55 + ((rect.size.height/2) * -1);

rect.origin = centerPoint;

[self.radiusView setFrame:rect];
}

And finally the radiusView object is a subclass of a UIView, that overrides the drawRect method to draw the translucent circles. setFrame is also over ridden in this UIView subclass, but it only serves to call [UIView setNeedsDisplay] in addition to [UIView setFrame:] to ensure that the view is redrawn after the frame has been updated.

The radiusView object's (CircleView) drawRect method looks like this

-(void) drawRect:(CGRect)rect{

//NSLog(@"[CircleView drawRect]");

[self setBackgroundColor:[UIColor clearColor]];
//Declarations
CGContextRef context;
CGMutablePathRef path;

//Assignments
context = UIGraphicsGetCurrentContext();

path = CGPathCreateMutable();

//Alter the rect so the circle isn't cliped

//Calculate the biggest size circle
if( rect.size.height > rect.size.width ){
    rect.size.height = rect.size.width;
}
else if( rect.size.height < rect.size.width ){
    rect.size.width = rect.size.height;
}

rect.size.height -= 4;
rect.size.width  -= 4;
rect.origin.x += 2;
rect.origin.y += 2;

//Create paths
CGPathAddEllipseInRect(path, NULL, rect );

//Create colors
[[self areaColor] setFill];

CGContextAddPath( context, path);
CGContextFillPath( context );

[[self borderColor] setStroke];

CGContextSetLineWidth( context, 2.0f );
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextAddPath(context, path );
CGContextStrokePath( context );
CGPathRelease( path );

//CGContextRestoreGState( context );

}

Thanks for bearing with me, any help is appreciated. Jonathan

Lowbred answered 22/5, 2010 at 13:44 Comment(0)
V
5

First, what's foo used in the first function? And I'm assuming radiusView's parent is the annotation view, right?

The "Jittering"

Also, the center point of radiusView should coincide with that of the annotationView. This should solve your problem:

-(void) setRadiusFrame:(CGRect)rect{
    rect.origin.x -= 0.5*(self.frame.size.width - rect.size.width);
    rect.origin.y -= 0.5*(self.frame.size.height - rect.size.height);
    [self.radiusView setFrame:rect]
}

Unnecessary method

You could set the frame directly on the radiusView and avoid the above calculation:

    UIView * radiusView = [[[self mapView] viewForAnnotation: annotation] radiusView];
    rect = [[self mapView] convertRegion:foo toRectToView: radiusView.superView];
    [radiusView setFrame:rect];
  1. When drawing the ellipse, don't use the rect passed to drawRect:, it doesn't have to be the same as the frame. It's safer to directly use self.frame

Unnecessary view

Now I gave the above points if you need to use the above hierarchy, but I don't see why don't you just draw your ellipses directly in the LocationAnnotationView? It's there for this purpose after all. This is how you do this:

  1. when scaling, change the annotationView's rect directly:

    rect = [[self mapView] convertRegion:foo toRectToView: self.mapView];
    [[[self mapView] viewForAnnotation: annotation] setFrame:rect];
    
  2. Move the implementation of drawRect: to LocationAnnotationView.

This is easier to implement, and should address your problem as the center point of the annotation view moves with your pin and you shouldn't see this problem.

Fixes

There are two other issues in the code:

  1. Set longitudeDelta like this:

    region.span.longitudeDelta = 0.002*cos(region.center.latitude*pi/180.0);
    

    as the longitude delta converted to meters changes with the latitude. Alternatively, you could only set latitude delta, then modify the rect so it becomes rectangular (width==height).

  2. in drawRect:, don't use the passed rect; instead use self.frame. There's no guarantee that these are the same, and rect could have any value.

Let me know if these work ;-)

Vadnee answered 2/6, 2010 at 23:1 Comment(2)
Q. First, what's foo used in the first function? A. Bad coding, i had left a variable called foo in there for testing, foo should be (MKRegion)region :) Q. And I'm assuming radiusView's parent is the annotation view, right? A. In terms of the view hierarchy, yes it is. Q. Unnecessary view A. The eclipses are colored according to proximity, so i wanted to separate the code. Also i wanted to be able to reuse them in a generic fashion. So using UIView is more convenient than using MKAnnotationView. I will try out your fixes, thanks so much for taking the time to answer!Lowbred
I obviously don't know your exact model, but I still think your problem with different colors could still be solved with a single view. After all there's a one-to-one relation between annotation, annotation view, and radiusView; either of the views could get the color from the annotation or something, so modularity shouldn't be hard to achieve. Anyways, I'm just thinking out loud, it doesn't matter that much ;-)Vadnee

© 2022 - 2024 — McMap. All rights reserved.