Posts Tagged ‘Maps’

Drawing polyines or routes on a MKMapView (as an MKAnnotationView) – Part 2

Wednesday, August 19th, 2009

It turns out there is a better way to display routes on a map than the last example I gave. The problem with my last example is that the route exists in a layer above the map view, resulting in the line of the route being drawn on top of any annotation views. So pins, photos and anything else expressed as an MKAnnotation would be drawn over by the routes in the layer above the map view.

The solution to this problem then is to draw the lines as usual, but in a custom MKAnnoationView. This view will be set to not clip its own subviews, and will then have a subview that lies outside the frame of the annotation view, and this subview will draw the lines. The route annotation view needs a subview to do the rendering which will always be positioned at the full frame size and origin of the map. This way the MKAnnotationView can be smaller than the route, but it  always draws in the internal subview, which is the size of the map view.

So… long story short: the ability to put a route on a MKMapView via a custom MKAnnotation. You can download the sample code here.

//
// CSRouteView.m
// testMapp
//
// Created by Craig on 8/18/09.
// Copyright Craig Spitzkoff 2009. All rights reserved.
//
#import "CSRouteView.h"
#import "CSRouteAnnotation.h"
// this is an internally used view to CSRouteView. The CSRouteView needs a subview that does not get clipped to always
// be positioned at the full frame size and origin of the map. This way the view can be smaller than the route, but it
// always draws in the internal subview, which is the size of the map view.
@interface CSRouteViewInternal : UIView
{
// route view which added this as a subview.
CSRouteView* _routeView;
}
@property (nonatomic, retain) CSRouteView* routeView;
@end
@implementation CSRouteViewInternal
@synthesize routeView = _routeView;
-(void) drawRect:(CGRect) rect
{
CSRouteAnnotation* routeAnnotation = (CSRouteAnnotation*)self.routeView.annotation;

// only draw our lines if we're not int he moddie of a transition and we
// acutally have some points to draw.
if(!self.hidden && nil != routeAnnotation.points && routeAnnotation.points.count > 0)
{
CGContextRef context = UIGraphicsGetCurrentContext();

if(nil == routeAnnotation.lineColor)
routeAnnotation.lineColor = [UIColor blueColor]; // setting the property instead of the member variable will automatically reatin it.

CGContextSetStrokeColorWithColor(context, routeAnnotation.lineColor.CGColor);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);

// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);

for(int idx = 0; idx < routeAnnotation.points.count; idx++)
{
CLLocation* location = [routeAnnotation.points objectAtIndex:idx];
CGPoint point = [self.routeView.mapView convertCoordinate:location.coordinate toPointToView:self];

NSLog(@"Point: %lf, %lf", point.x, point.y);

if(idx == 0)
{
// move to the first point
CGContextMoveToPoint(context, point.x, point.y);
}
else
{
CGContextAddLineToPoint(context, point.x, point.y);
}
}

CGContextStrokePath(context);

// debug. Draw the line around our view.
/*
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 0, self.frame.size.height);
CGContextAddLineToPoint(context, self.frame.size.width, self.frame.size.height);
CGContextAddLineToPoint(context, self.frame.size.width, 0);
CGContextAddLineToPoint(context, 0, 0);
CGContextStrokePath(context);
*/
}

}
-(id) init
{
self = [super init];
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = NO;

return self;
}
-(void) dealloc
{
self.routeView = nil;

[super dealloc];
}
@end
@implementation CSRouteView
@synthesize mapView = _mapView;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
// do not clip the bounds. We need the CSRouteViewInternal to be able to render the route, regardless of where the
// actual annotation view is displayed.
self.clipsToBounds = NO;

// create the internal route view that does the rendering of the route.
_internalRouteView = [[CSRouteViewInternal alloc] init];
_internalRouteView.routeView = self;

[self addSubview:_internalRouteView];
}
return self;
}
-(void) setMapView:(MKMapView*) mapView
{
[_mapView release];
_mapView = [mapView retain];

[self regionChanged];
}
-(void) regionChanged
{
NSLog(@"Region Changed");

// move the internal route view.
CGPoint origin = CGPointMake(0, 0);
origin = [_mapView convertPoint:origin toView:self];

_internalRouteView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height);
[_internalRouteView setNeedsDisplay];

}
- (void)dealloc
{
[_mapView release];
[_internalRouteView release];

[super dealloc];
}
@end

Using MKAnnotation, MKPinAnnotationView and creating a custom MKAnnotationView in an MKMapView

Saturday, May 16th, 2009

My last experiment maps on the iPhone using the MKMapView from the iPhone’s MapKit was an example of how to use the MKMapView to display the line of a route on the map.

Today’s experiment will demonstrate how to drop pin annotation views, as well as custom annotation views on the iphone’s map by providing the map with objects that implement the MKAnnotation protocol.

The class I created that implements MKAnnotation can be used to differentiate between those annotations that should be represented by MKPinAnnotationViews of diferernt colors, as well as our custom annotation view, CSImageAnnotationView. The CSMapAnnotation class needs to keep track of additional information such as images and URLs that can be used for a custom display.

This is the header for the custom annotation. You can see that there is an enumeration that is used to designate what type of annotation view should be used for display of each annotation on the iPhone’s map. Also available are the userData and url properties; userData can be used to store anything, but in the case of this example, we’re using it to store path information for an image that should be displayed on the map. Title is also an available property, but subtitle will be generated on the fly.

//
// CSMapAnnotation.h
// mapLines
//
// Created by Craig on 5/15/09.
// Copyright 2009 Craig Spitzkoff. All rights reserved.
//

#import
#import

// types of annotations for which we will provide annotation views.
typedef enum {
CSMapAnnotationTypeStart = 0,
CSMapAnnotationTypeEnd = 1,
CSMapAnnotationTypeImage = 2
} CSMapAnnotationType;

@interface CSMapAnnotation : NSObject
{
CLLocationCoordinate2D _coordinate;
CSMapAnnotationType _annotationType;
NSString* _title;
NSString* _subtitle;
NSString* _userData;
NSURL* _url;
}

-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate
annotationType:(CSMapAnnotationType) annotationType
title:(NSString*)title;

@property CSMapAnnotationType annotationType;
@property (nonatomic, retain) NSString* userData;
@property (nonatomic, retain) NSURL* url;

@end

In most cases, when our MKMapViewDelegate is asked for an MKAnnotation, we return a MKPinAnnotation. If the CSMapAnnotation is set to be CSMapAnnotationTypeImage , the delegate instead produces an instance of our custom annotation view, CSImageAnnotationView, and sets its properties based on the annotation’s user data.

This is the body of our custom annotation view. It is pretty simple; the bulk of the functionality is in the initialization function, which sets up the view to have a UIImageView.

//
// CSImageAnnotationView.m
// mapLines
//
// Created by Craig on 5/15/09.
// Copyright 2009 Craig Spitzkoff. All rights reserved.
//

#import "CSImageAnnotationView.h"
#import "CSMapAnnotation.h"

#define kHeight 100
#define kWidth 100
#define kBorder 2

@implementation CSImageAnnotationView
@synthesize imageView = _imageView;

- (id)initWithAnnotation:(id )annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
self.frame = CGRectMake(0, 0, kWidth, kHeight);
self.backgroundColor = [UIColor whiteColor];

CSMapAnnotation* csAnnotation = (CSMapAnnotation*)annotation;

UIImage* image = [UIImage imageNamed:csAnnotation.userData];
_imageView = [[UIImageView alloc] initWithImage:image];

_imageView.frame = CGRectMake(kBorder, kBorder, kWidth - 2 * kBorder, kWidth - 2 * kBorder);
[self addSubview:_imageView];

return self;

}

-(void) dealloc
{
[_imageView release];
[super dealloc];
}

@end

So, how does the map know to ask the delegate for annotation views? It knows to do this based on annotations that are added to the map in our view controller’s viewDidLoad method.


// create the image annotation
annotation = [[[CSMapAnnotation alloc] initWithCoordinate:[[points objectAtIndex:points.count / 2] coordinate]
annotationType:CSMapAnnotationTypeImage
title:@"Cleveland Circle"] autorelease];
[annotation setUserData:@"cc.jpg"];
[annotation setUrl:[NSURL URLWithString:@"http://en.m.wikipedia.org/wiki/Cleveland_Circle"]];

[_mapView addAnnotation:annotation];

UPDATE – 6/1/2009
You’ll notice we set the URL on the annotation above, and the code has now been updated to display that webpage when the user clicks the annotation disclosure of our custom image annotation view. The code sample you can download has been updated to reflect this change.

The full code of this example is available for download here.

Update – 8/19/2009
Another update. This one displays the route as an annotation view, so it does not render on top of other annotations.You can view the update at this link.