Displaying Maps
include an#import <MapKit/MapKit.h>
Map Kit supports three basic coordinate systems for specifying map data points:
-
A map coordinate is a latitude and longitude on the spherical representation of the Earth. Map coordinates are the primary way of specifying locations on the globe. You specify individual map coordinate values using the
CLLocationCoordinate2D
structure. You can specify areas using theMKCoordinateSpan
andMKCoordinateRegion
structures. // 经纬度 -
A map point is an x and y value on the Mercator map projection. Map points are used for many map-related calculations instead of map coordinates because they simplify the mathematics involved in the calculations. In your app, you use map points primarily when specifying the shape and position of custom map overlays. You specify individual map points using the
MKMapPoint
structure. You can specify areas using theMKMapSize
andMKMapRect
structures. // 地图maprect 将地球仪平铺成的map视图坐标 -
A point is a graphical unit associated with the coordinate system of a
UIView
object. Map points and map coordinates must be mapped to points before drawing custom content in a view. You specify individual points using theCGPoint
structure. You can specify areas using theCGSize
andCGRect
structures. //view
Convert from | Convert to | Conversion routines |
---|---|---|
Map coordinates | Points | |
Map coordinates | Map points | |
Map points | Map coordinates | |
Map points | Points | |
Points | Map coordinates | |
Points | Map points | |
Adding a Map View to Your User Interface
To add a map programmatically, create an instance of theMKMapView
class,
initialize
it using the
initWithFrame:
method, and then add it as a subview to your view hierarchy.
frame
property and do not scroll with the map contents. If you want content to remain fixed relative to a specific map coordinate (and thus scroll with the map itself), you must use annotations or overlays as described in
“Annotating Maps.”
region
property (or set using the setRegion:animated:
method) is usually not the same value that is eventually stored by that property.Setting the span of a region nominally defines the rectangle you want to view but also implicitly sets the zoom level for the map view itself. The map view cannot display arbitrary zoom levels and must adjust any regions you specify to match the zoom levels it supports. It chooses the zoom level that allows your entire region to be visible while still filling as much of the screen as possible. It then adjust the region
property accordingly. To find out the resulting region without actually changing the value in the region
property, you can use the regionThatFits:
method of the map view.
Zooming and Panning the Map Content
Zooming and panning allow you to change the visible portion of the map at any time:
-
To pan the map (but keep the same zoom level), change the value in the
centerCoordinate
property of the map view or call thesetCenterCoordinate:animated:
method. -
To change the zoom level (and optionally pan the map), change the value in the
region
property of the map view or call thesetRegion:animated:
method.
Displaying the User’s Current Location on the Map
Map Kit includes built-in support for displaying the user’s current location on the map. To show this location, set the showsUserLocation
property of your map view object to YES
. Doing so causes the map view to use Core Location to find the user’s location and add an annotation of type MKUserLocation
to the map.
The addition of the MKUserLocation
annotation object to the map is reported by the delegate in the same way that custom annotations are. If you want to associate a custom annotation view with the user’s location, you should return that view from your delegate object’s mapView:viewForAnnotation:
method. If you want to use the default annotation view, you should return nil
from that method instead.
Responding to User Interactions with a Map
The MKMapView
class reports significant map-related events to its associated delegate object. The delegate object is an object that conforms to the MKMapViewDelegate
protocol. You provide this object in situations where you want to respond to the following types of events:
-
Changes to the visible region of the map
-
The loading of map tiles from the network
-
Changes in the user’s location
-
Changes associated with annotations and overlays.
Annotating Maps
In order to display an annotation on a map, your app must provide two distinct objects:
-
An object that conforms to the
MKAnnotation
protocol and manages the data for the annotation. (This object is the annotation object.) -----数据 -
A view (derived from the
MKAnnotationView
class) used to draw the visual representation of the annotation on the map surface. (This is the annotation view.)-------视图
-
Define an appropriate annotation object using one of the following options:
-
Use the
MKPointAnnotation
class to implement a simple annotation. This type of annotation contains properties for specifying the title and subtitle strings to display in the annotation’s onscreen callout bubble. -
Define a custom object that conforms to the
MKAnnotation
protocol, as described in “Defining a Custom Annotation Object.” This type of annotation can store any type of data you want.
-
-
Define an annotation view to present the data on screen. How you define your annotation view depends on your needs and may be one of the following:
-
If the annotation can be represented by a static image, create an instance of the
MKAnnotationView
class and assign the image to itsimage
property; see “Using the Standard Annotation Views.” -
If you want to use a standard pin annotation, create an instance of the
MKPinAnnotationView
class; see“Using the Standard Annotation Views.” -
If a static image is insufficient for representing your annotation,subclass
MKAnnotationView
and implement the custom drawing code needed to present it.For information about how to implement custom annotation views, see “Defining a Custom Annotation View.”
-
-
Implement the
mapView:viewForAnnotation:
method in your map view delegate.Your implementation of this method should dequeue an existing annotation view if one exists or create a new one. If your app supports multiple types of annotations, you must include logic in this method to create a view of the appropriate type for the provided annotation object. For more information about implementing this method, see “Creating Annotation Views from Your Delegate Object.”
-
Add your annotation object to the map view using the
addAnnotation:
oraddAnnotations:
method.
Defining a Custom Annotation Object
Creating a simple annotation object
@interface MyCustomAnnotation : NSObject <MKAnnotation> { |
CLLocationCoordinate2D coordinate; |
} |
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate; |
- (id)initWithLocation:(CLLocationCoordinate2D)coord; |
|
// Other methods and properties. |
@end |
@implementation MyCustomAnnotation |
@synthesize coordinate; |
|
- (id)initWithLocation:(CLLocationCoordinate2D)coord { |
self = [super init]; |
if (self) { |
coordinate = coord; |
} |
return self; |
} |
@end |
Using the Standard Annotation Views
MKAnnotationView* aView = [[[MKAnnotationView alloc] initWithAnnotation:annotation |
reuseIdentifier:@"MyCustomAnnotation"] autorelease]; |
aView.image = [UIImage imageNamed:@"myimage.png"]; |
aView.centerOffset = CGPointMake(10, -20); |
Defining a Custom Annotation View
Declaring a custom annotation view
#import <UIKit/UIKit.h> |
#import <MapKit/MapKit.h> |
|
@interface MyCustomAnnotationView : MKAnnotationView |
{ |
// Custom data members |
} |
// Custom properties and methods. |
@end |
Initializing a custom annotation view
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier |
{ |
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; |
if (self) |
{ |
// Set the frame size to the appropriate values. |
CGRect myFrame = self.frame; |
myFrame.size.width = 40; |
myFrame.size.height = 40; |
self.frame = myFrame; |
|
// The opaque property is YES by default. Setting it to |
// NO allows map content to show through any unrendered |
// parts of your view. |
self.opaque = NO; |
} |
return self; |
} |
Creating Annotation Views from Your Delegate Object
Creating annotation views
- (MKAnnotationView *)mapView:(MKMapView *)mapView |
viewForAnnotation:(id <MKAnnotation>)annotation |
{ |
// If it's the user location, just return nil. |
if ([annotation isKindOfClass:[MKUserLocation class]]) |
return nil; |
|
// Handle any custom annotations. |
if ([annotation isKindOfClass:[MyCustomAnnotation class]]) |
{ |
// Try to dequeue an existing pin view first. |
MKPinAnnotationView* pinView = (MKPinAnnotationView*)[mapView |
dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotationView"]; |
|
if (!pinView) |
{ |
// If an existing pin view was not available, create one. |
pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation |
reuseIdentifier:@"CustomPinAnnotation"] |
autorelease]; |
pinView.pinColor = MKPinAnnotationColorRed; |
pinView.animatesDrop = YES; |
pinView.canShowCallout = YES; |
|
// Add a detail disclosure button to the callout. |
UIButton* rightButton = [UIButton buttonWithType: |
UIButtonTypeDetailDisclosure]; |
[rightButton addTarget:self action:@selector(myShowDetailsMethod:) |
forControlEvents:UIControlEventTouchUpInside]; |
pinView.rightCalloutAccessoryView = rightButton; |
} |
else |
pinView.annotation = annotation; |
|
return pinView; |
} |
|
return nil; |
} |
- (void)animateToWorld:(WorldCity *)worldCity
{
MKCoordinateRegion current = mapView.region;
MKCoordinateRegion zoomOut = { { (current.center.latitude + worldCity.coordinate.latitude)/2.0 , (current.center.longitude + worldCity.coordinate.longitude)/2.0 }, {90, 90} };
[mapView setRegion:zoomOut animated:YES];
}
- (void)animateToPlace:(WorldCity *)worldCity
{
MKCoordinateRegion region;
region.center = worldCity.coordinate;
MKCoordinateSpan span = {0.4, 0.4};
region.span = span;
[mapView setRegion:region animated:YES];
}
- (void)worldCitiesListController:(WorldCitiesListController *)controller didChooseWorldCity:(WorldCity *)aPlace
{
[self.navigationController dismissModalViewControllerAnimated:YES];
self.title = aPlace.name;
MKCoordinateRegion current = mapView.region;
if (current.span.latitudeDelta < 10)
{
[self performSelector:@selector(animateToWorld:) withObject:aPlace afterDelay:0.3];
[self performSelector:@selector(animateToPlace:) withObject:aPlace afterDelay:1.7];
}
else
{
[self performSelector:@selector(animateToPlace:) withObject:aPlace afterDelay:0.3];
}
}
Managing the Map’s Annotation Objects
The only way to eliminate annotation overcrowding is to remove some of the annotation objects from the map view. This typically involves implementing themapView:regionWillChangeAnimated:
and
mapView:regionDidChangeAnimated:
methods to detect changes in the map zoom level. During a zoom change, you can add or remove annotations as needed based on their proximity to one another. You might also consider other criteria (such as the user’s current location) to eliminate some annotations.
MKMetersBetweenMapPoints
method to get absolute distances between two points. You can also use each coordinate as the center of a map rectangle and use the MKMapRectIntersectsRect
function to find any intersections. For a complete list of functions, see Map Kit Functions Reference.Marking Your Annotation View as Draggable
-
In your annotation objects, implement the
setCoordinate:
method to allow the map view to update the annotation’s coordinate point. -
When creating your annotation view, set its
draggable
property toYES
.
mapView:annotationView:didChangeDragState:fromOldState:
method of its delegate to notify it of changes to the drag state of your view. You can use this method to affect or respond to the drag operation
dragState
method in your annotation view. As the map view processes drag-related touch events, it updates the dragState
property of the affected annotation view. Implementing a custom dragState
method gives you a chance to intercept these changes and perform additional actions, such as animate the appearance of your view. For example, the MKPinAnnotationView
class raises the pin off the map when a drag operation starts and drops the pin back down on the map when it ends.
In order to display an overlay on a map, your app must provide two distinct objects:
-
An object that conforms to the
MKOverlay
protocol and manages the data points for the overlay. (This object is the overlay object.) -
A view (derived from the
MKOverlayView
class) used to draw the visual representation of the overlay on the map surface. (This is the overlay view.)
Checklist for Adding an Overlay to the Map
-
Define an appropriate overlay data object using one of the following options:
-
Use the
MKCircle
,MKPolygon
, orMKPolyline
class as-is. -
Subclass
MKShape
orMKMultiPoint
to create overlays that provide app-specific behaviors or use custom shapes. -
Use an existing class from your app and make it conform to the
MKOverlay
protocol.
-
-
Define an overlay view to present on the screen using one of the following options:
-
For standard shapes, use the
MKCircleView
,MKPolygonView
, orMKPolylineView
to represent the annotation. You can customize many of the drawing attributes of the final shape using these classes. -
For custom shapes descended from
MKShape
, define an appropriate subclass ofMKOverlayPathView
to render the shape. -
For all other custom shapes and overlays, subclass
MKOverlayView
and implement your custom drawing code.
-
-
Implement the
mapView:viewForOverlay:
method in your map view delegate. -
Add your overlay data object to the map view using the
addOverlay:
method or one of many others.
Using the Standard Overlay Objects and Views
Creating a polygon overlay object
// Define an overlay that covers Colorado. |
CLLocationCoordinate2D points[4]; |
|
points[0] = CLLocationCoordinate2DMake(41.000512, -109.050116); |
points[1] = CLLocationCoordinate2DMake(41.002371, -102.052066); |
points[2] = CLLocationCoordinate2DMake(36.993076, -102.041981); |
points[3] = CLLocationCoordinate2DMake(36.99892, -109.045267); |
|
MKPolygon* poly = [MKPolygon polygonWithCoordinates:points count:4]; |
poly.title = @"Colorado"; |
|
[map addOverlay:poly]; |
Creating a polygon view for rendering a shape
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay |
{ |
if ([overlay isKindOfClass:[MKPolygon class]]) |
{ |
MKPolygonView* aView = [[[MKPolygonView alloc] initWithPolygon:(MKPolygon*)overlay] autorelease]; |
|
aView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2]; |
aView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7]; |
aView.lineWidth = 3; |
|
return aView; |
} |
|
return nil; |
} |
Defining a Custom Overlay Object
You can subclassMKShape
or
MKMultiPoint
to define new types of shape-based overlays or you can adopt the
MKOverlay
protocol into one of your app’s existing classes.
Whether you subclass or adopt the MKOverlay
protocol, the work you have to do in any custom overlay object is the same. The main job of an overlay object is to vend two key pieces of information:
-
A coordinate defining the center point of the overlay
-
A bounding rectangle that completely encompasses the overlay’s content
Defining a Custom Overlay View
To create a custom overlay view, you must subclass MKOverlayView
. (If you simply want to modify the drawing behavior of an existing shape-based overlay, you can subclass MKOverlayPathView
instead.) In your custom implementation, you should implement the following methods:
-
drawMapRect:zoomScale:inContext:
to draw your custom content //实际❀画图的方法 -
canDrawMapRect:zoomScale:
if your drawing code depends on content that might not always be available
setNeedsDisplayInMapRect:
or
setNeedsDisplayInMapRect:zoomScale:
method.
Unlike drawing in a normal view, drawing in an overlay view involves some special considerations, including the following:
-
Your drawing code should never use the view’s bounds or frame as reference points for drawing. Instead, it should use the map points associated with the overlay object to define shapes. Immediately before drawing, it should then convert those map points to points (
CGPoint
and so on) using the conversion routines found in theMKOverlayView
class.Also, you typically do not apply the zoom scale value passed to this method directly to your content. Instead, you provide it only when a Map Kit function or method specifically requires it. As long as you specify content using map points and convert to points, your content should be scaled to the correct size automatically.
-
If you use UIKit classes and functions to draw, you must explicitly set up and clean up the drawing environment. Before issuing any calls, call the
UIGraphicsPushContext
function to make the context passed to your method the current context. When you are done drawing, callUIGraphicsPopContext
to remove that context. -
Remember that the map view may tile large overlays and render each tile on a separate thread. Your drawing code should therefore not attempt to modify variables or other data unless it can do so in a thread-safe manner.
Drawing a gradient in a custom overlay view
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context |
{ |
// Get the overlay bounding rectangle. |
MKMapRect theMapRect = [self.overlay boundingMapRect]; |
CGRect theRect = [self rectForMapRect:theMapRect]; |
|
// Clip the context to the bounding rectangle. |
CGContextAddRect(context, theRect); |
CGContextClip(context); |
|
// Set up the gradient color and location information. |
CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB(); |
CGFloat locations[4] = {0.0, 0.33, 0.66, 1.0}; |
CGFloat components[16] = {0.0, 0.0, 1.0, 0.5, |
1.0, 1.0, 1.0, 0.8, |
1.0, 1.0, 1.0, 0.8, |
0.0, 0.0, 1.0, 0.5}; |
|
// Create the gradient. |
CGGradientRef myGradient = CGGradientCreateWithColorComponents(myColorSpace, components, locations, 4); |
CGPoint start, end; |
start = CGPointMake(CGRectGetMidX(theRect), CGRectGetMinY(theRect)); |
end = CGPointMake(CGRectGetMidX(theRect), CGRectGetMaxY(theRect)); |
|
// Draw. |
CGContextDrawLinearGradient(context, myGradient, start, end, 0); |
|
// Clean up. |
CGColorSpaceRelease(myColorSpace); |
CGGradientRelease(myGradient); |
} |
Managing the Map’s Overlay Objects
In cases where the bounding rectangles of two overlays do overlap, you can either remove one of the overlays or arrange their Z-order to control which one appears on top.mapView:didAddOverlayViews:
method of your map view delegate. When this method is called, you can use the MKMapRectIntersectsRect
function to see if the added overlay intersects the bounds of any other overlays. Using Overlays as Annotations
TheMKOverlay
protocol conforms to the
MKAnnotation
protocol. As a result, all overlay objects are also annotation objectsand can be treated as one or both in your code. If you opt to treat an overlay object as both, you are responsible for managing that object in two places.If you want to display both an overlay view and annotation view for it, you must implement both the
mapView:viewForOverlay:
andmapView:viewForAnnotation:
methods in your app delegate. It also means that you must add and remove the object from bot
h the overlays
and annotations
arrays
of your map.