您的位置:首页 > 其它

使用MapKit叠加图层(raywenderlich翻译)(下)

2013-03-12 15:18 218 查看
如果你喜欢的话那就在地图上放置一个Pin — 注解
如果你用Maps程序搜索过位置信息,那么你肯定看到过在地图上出现的许多Pin。这可以理解为注解(annotation),它是用 MKAnnotationView创建的。你也可以在你的程序中使用注解 — 并使用你想要的任何图片,不仅仅是pin!
在程序中使用注解来标出具体的某个景点,这对游客来说非常有用。注解对象的使用方法跟MKOverlay 和 MKOverlayView非常类似, 只不过需要使用的类是MKAnnotation 和 MKAnnotationView。
在Annotations群组中创建一个名为 PVAttractionAnnotation 新的类,并继承自 NSObject。然后用下面的代码替换 PVAttractionAnnotation.h 文件中的内容:
#import

#import



typedef NS_ENUM(NSInteger, PVAttractionType) {

PVAttractionDefault = 0,

PVAttractionRide,

PVAttractionFood,

PVAttractionFirstAid

};



@interface PVAttractionAnnotation : NSObject



@property (nonatomic) CLLocationCoordinate2D coordinate;

@property (nonatomic, strong) NSString *title;

@property (nonatomic, strong) NSString *subtitle;

@property (nonatomic) PVAttractionType type;



@end


上面的代码中,首先是import MapKit,然后为PVAttractionType.定义了一个枚举。这个枚举列出了注解的类型: 游乐设施,食物,急救和默认。



接着让这个类遵循 MKAnnotation Protocol 跟MKOverlay类似, MKAnnotation 有一个 required coordinate 属性. 最后是定义了一些属性。OK, 下面我们来看看PVAttractionAnnotation的实现。
将 PVAttractionAnnotation.m 按照如下修改:
#import

#import



@interface PVAttractionAnnotationView : MKAnnotationView



@end

将下面的代码添加到 PVAttractionAnnotationView.m:

#import "PVAttractionAnnotationView.h"

#import "PVAttractionAnnotation.h"



@implementation PVAttractionAnnotationView



- (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier {

self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];

if (self) {

PVAttractionAnnotation *attractionAnnotation = self.annotation;

switch (attractionAnnotation.type) {

case PVAttractionFirstAid:

self.image = [UIImage imageNamed:@"firstaid"];

break;

case PVAttractionFood:

self.image = [UIImage imageNamed:@"food"];

break;

case PVAttractionRide:

self.image = [UIImage imageNamed:@"ride"];

break;

default:

self.image = [UIImage imageNamed:@"star"];

break;

}

}



return self;

}



@end


上面重载了方法 initWithAnnotation:reuseIdentifier:; 根据注解不同的type属性,为注解设置不同的image属性。现在你创建好了注解和与其相关的view,下面可以将它们添加到map view中了!
首先,你需要准备在 initWithAnnotation:reuseIdentifier:方法中用到的一些资源,以及经典相关的位置信息(MagicMountainAttractions.plist). 这些资源包含在 resources for this tutorial – 将它们拷贝到工程的Images群组中。
在plist文件中包含了坐标信息以及其它与公园景点相关的一些详细信息,如下所示:










name

Goliath

location

{34.42635,-118.59712}

type

1

subtitle

Intensity: 8/10





name

Batman

location

{34.42581,-118.60089}

type

1

subtitle

Intensity: 6/10





name

Ridler's Revenge

location

{34.42430,-118.60074}

type

1

subtitle

Intensity: 6/10





name

X2

location

{34.42156,-118.59556}

type

1

subtitle

Intensity: 10/10





name

Tatsu

location

{34.42150,-118.59741}

type

1

subtitle

Intensity: 7/10





name

Panda Express

location

{34.42126,-118.595637}

type

2

subtitle

Cost: $$





name

Cold Stone

location

{34.42401,-118.59495}

type

2

subtitle

Cost: $





name

First Aid

location

{34.42640,-118.59918}

type

3

subtitle

Call 911 For Emergency




现在你以及拥有上面这些资源了,当然你也可以使用新的注解!回到 PVParkMapViewController.m 并import MKAnnotation 和 MKAnnotationView两个类, 如下所示:
#import "PVAttractionAnnotation.h"

#import "PVAttractionAnnotationView.h"


下面定义一个方法,将景点的注解添加到map view中:

添加如下方法(还是在PVParkMapViewController.m):

- (void)addAttractionPins {

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"MagicMountainAttractions" ofType:@"plist"];

NSArray *attractions = [NSArray arrayWithContentsOfFile:filePath];

for (NSDictionary *attraction in attractions) {

PVAttractionAnnotation *annotation = [[PVAttractionAnnotation alloc] init];

CGPoint point = CGPointFromString(attraction[@"location"]);

annotation.coordinate = CLLocationCoordinate2DMake(point.x, point.y);

annotation.title = attraction[@"name"];

annotation.type = [attraction[@"type"] integerValue];

annotation.subtitle = attraction[@"subtitle"];

[self.mapView addAnnotation:annotation];

}

}


上面的个方法读取 MagicMountainAttractions.plist 文件,并对字典数组进行枚举. 针对每个条目,都使用相关的景点信息来创建一个 PVAttractionAnnotation 实例,并将它们添加到map view中。现在需要更新一下 loadSelectedOptions 方法,当被选中时,以匹配新的选项,并执行新的方法。
将下面的代码添加到 loadSelectedOptions 中(仍然在 PVParkMapViewController.m):
- (void)loadSelectedOptions {

[self.mapView removeAnnotations:self.mapView.annotations];

[self.mapView removeOverlays:self.mapView.overlays];

for (NSNumber *option in self.selectedOptions) {

switch ([option integerValue]) {

case PVMapOverlay:

[self addOverlay];

break;

case PVMapPins:

[self addAttractionPins];

break;

default:

break;

}

}

}
现在快完成了!不过还差一点,你需要实现另外一个delegate方法,这个delegate方法给map view提供一个MKAnnotationView 实例,这样才能够进行渲染。
将下面的代码添加到 PVParkMapViewController.m:

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

PVAttractionAnnotationView *annotationView = [[PVAttractionAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"Attraction"];

annotationView.canShowCallout = YES;

return annotationView;

}
上面的方法会接受一个 MKAnnotation ,并用此来创建一个 PVAttractionAnnotationView. 它的canShowCallout 属性设置为YES,这样当用户触摸到这个注解时,会显示出更多的信息,最后将这个注解返回!

启动景点Pin,看看结果是什么样,如下图:



景点pin在这里看起来有点不圆滑!至此,已经介绍了MapKit许多复杂的知识了,包括overlays和annotation。不过如果你希望绘制一些基本的图形呢:线条、形状和圆等?使用MapKit framework同样可以在map view上进行绘制!MapKit提供的MKPolyline, MKPolygon 和 MKCircle 就可以做到。
我走的线路 – MKPolyline
如果你去过魔山,你应该知道 Goliath hypercoaster 是一个非常令人难以置信的旅程, 当骑手们跨进了大门,就想一路狂奔!为了帮助这些骑手们,你绘制了一条从公园入口到Goliath的路径。


通过MKPolyline 可以非常方便的画一条连接多个点的线,比如绘制一条从A到B的非直线路径。在程序中用 MKPolyline 绘制路径,Goliath的粉丝们会更加快捷的骑行。!需要一系列的经纬度坐标信息,这些坐标将用来按顺序的进行绘制。在这里顺序非常重要 — 否则,绘制出来的线路会是无用的,对骑手们来说毫无意义!



resources for this tutorial中包含了一个叫 EntranceToGoliathRoute.plist 的文件,里面有路径信息,将这个文件添加到工程中。这个plist文件中的内容如下:

01.

02.

03.

04.

05. {34.42367,-118.594836}

06. {34.423597,-118.595205}

07. {34.423004,-118.59537}

08. {34.423044,-118.595806}

09. {34.423419,-118.596126}

10. {34.423569,-118.596229}

11. {34.42382,-118.596192}

12. {34.42407,-118.596283}

13. {34.424323,-118.596534}

14. {34.42464,-118.596858}

15. {34.42501,-118.596838}

16. {34.42537,-118.596688}

17. {34.425690,-118.596683}

18. {34.42593,-118.596806}

19. {34.42608,-118.597101}

20. {34.42634,-118.597094}

21.

22.


如上,属性列表是一个简单的字符串数组,字符串中包含了路径中每个点的经度和纬度信息。现在,你需要写一个方法来读取plist文件中的内容,并为骑手们创建出一条路。



将下面的代码添加到 PVParkMapViewController.m:

- (void)addRoute {

NSString *thePath = [[NSBundle mainBundle] pathForResource:@"EntranceToGoliathRoute" ofType:@"plist"];

NSArray *pointsArray = [NSArray arrayWithContentsOfFile:thePath];



NSInteger pointsCount = pointsArray.count;



CLLocationCoordinate2D pointsToUse[pointsCount];



for(int i = 0; i < pointsCount; i++) {

CGPoint p = CGPointFromString(pointsArray[i]);

pointsToUse[i] = CLLocationCoordinate2DMake(p.x,p.y);

}



MKPolyline *myPolyline = [MKPolyline polylineWithCoordinates:pointsToUse count:pointsCount];



[self.mapView addOverlay:myPolyline];
上面这个方法会读取 EntranceToGoliathRoute.plist, 并遍历其中包含的内容,然后将信息单独坐标字符串转换为 CLLocationCoordinate2D 结构。


很明显,在程序中绘制折线是很简单的;你创建一个数组,数组里面包含所有的点,并将这个数组传递给 MKPolyline! 没有比这更简单的了。



下面,你需要添加一个选项,允许用户打开或者关闭折线路径。用下面的代码更新一下 loadSelectedOptions 方法:

- (void)loadSelectedOptions {

[self.mapView removeAnnotations:self.mapView.annotations];

[self.mapView removeOverlays:self.mapView.overlays];

for (NSNumber *option in self.selectedOptions) {

switch ([option integerValue]) {

case PVMapOverlay:

[self addOverlay];

break;

case PVMapPins:

[self addAttractionPins];

break;

case PVMapRoute:

[self addRoute];

break;

default:

break;

}

}

}


最后,为了将所有的这些内容结合在一起,需要更新一下delegate方法—返回在map view中实际需要渲染的view。更新一下 mapView:viewForOverlay: 方法,以能够处理折线图层的情况,如下:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay {

if ([overlay isKindOfClass:PVParkMapOverlay.class]) {

UIImage *magicMountainImage = [UIImage imageNamed:@"overlay_park"];

PVParkMapOverlayView *overlayView = [[PVParkMapOverlayView alloc] initWithOverlay:overlay overlayImage:magicMountainImage];



return overlayView;

} else if ([overlay isKindOfClass:MKPolyline.class]) {

MKPolylineView *lineView = [[MKPolylineView alloc] initWithOverlay:overlay];

lineView.strokeColor = [UIColor greenColor];



return lineView;

}



return nil;

}


折线图层显示的处理跟之前的overlay view十分类似。只不过,绘制折线的时候,不需要提供自己的view对象。只需要使用 MKPolyLineView 即可—用overlay初始化一个新的实例即可。



MKPolyLineView 同样提供可以修改折线的一些属性。比如,可以把折线的颜色修改为绿色。



编译并运行程序,将route选项打开,出现在屏幕中的内容如下图所示:


Goliath的粉丝们现在可以利用这个绘制的路径去创造新的记录啦!要是能够实际的显示出公园的边界范围就太棒了——因为公园实际上并不是完整的占据整个屏幕。

虽然可以使用 MKPolyline 来绘制公园的边界,不过MapKit提供了另外一个类专门来绘制封闭的多边形:MKPolygon。
不要围着我 – MKPolygon
MKPolygon 跟MKPolyline非常相似(坐标集合中除了第一个点和最后一个点是连接以外)。可以创建一个 MKPolygon ,将其当做显示公园边界的一个overylay。公园的边界坐标已经定义在the MagicMountain.plist中了; 可以返回之前的内容看看 initWithFilename: 方法是如何从plist文件中读取出边界点的。
将下面的代码添加到 PVParkMapViewController.m:
- (void)addBoundary {

MKPolygon *polygon = [MKPolygon polygonWithCoordinates:self.park.boundary

count:self.park.boundaryPointsCount];

[self.mapView addOverlay:polygon];

}
addBoundary 方法的实现非常简单。从park实例中制定边界数组和边界点数,这样就可以很快捷的创建一个新的 MKPolygon 实例了!

你能猜猜下一步要做什么吗?跟上面的MKPolyline 操作非常类似。更新一下 loadSelectedOptions 方法,以能够显示或者隐藏公园的边界,如下所示:
- (void)loadSelectedOptions {

[self.mapView removeAnnotations:self.mapView.annotations];

[self.mapView removeOverlays:self.mapView.overlays];

for (NSNumber *option in self.selectedOptions) {

switch ([option integerValue]) {

case PVMapOverlay:

[self addOverlay];

break;

case PVMapPins:

[self addAttractionPins];

break;

case PVMapRoute:

[self addRoute];

break;

case PVMapBoundary:

[self addBoundary];

break;

default:

break;

}

}

}


MKPolygon 跟MKPolyline一样,遵循 MKOverlay ,因此也需要更新一下相关的delegate方法。



在PVParkMapViewController.m中更新一下delegate方法:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay {

if ([overlay isKindOfClass:PVParkMapOverlay.class]) {

UIImage *magicMountainImage = [UIImage imageNamed:@"overlay_park"];

PVParkMapOverlayView *overlayView = [[PVParkMapOverlayView alloc] initWithOverlay:overlay overlayImage:magicMountainImage];



return overlayView;

} else if ([overlay isKindOfClass:MKPolyline.class]) {

MKPolylineView *lineView = [[MKPolylineView alloc] initWithOverlay:overlay];

lineView.strokeColor = [UIColor greenColor];



return lineView;

} else if ([overlay isKindOfClass:MKPolygon.class]) {

MKPolygonView *polygonView = [[MKPolygonView alloc] initWithOverlay:overlay];

polygonView.strokeColor = [UIColor magentaColor];



return polygonView;

}



return nil;

}
如上所示,delegate的更新方式跟之前一样简单。创建一个MKPolygonView实例,并将其颜色设置为magenta.编译并运行程序,将看到公园的边界!


上面就是折线和多边形的绘制。下面我将介绍最后一种绘制——使用MKCircle绘制圆
沙滩上的圈圈 – MKCircle
MKCircle 跟 MKPolyline 和 MKPolygon,同样非常相似。只不过它是根据给定的中心坐标和半径来绘制一个圆。
可以想象一下,在公园里,用户可能希望在地图上做一下标注,跟将这个标注与他人分享。此时,就可以使用一个圆来代表用户的标注。

在本文中,你不会走的很远,不过,最起码的,你可以从文件中加载一些坐标数据,并在地图上绘制出一些圆,来当做用户在地图上的一些标注。

MKCircle overlay 可以很容易的就实现这个功能.

resources for this tutorial包含了相关标注的位置信息,只需要将其添加到工程中。

每个文件包含了一些坐标信息。
{34.42481,-118.596914}

{34.423383,-118.596101}

{34.423628,-118.595197}

{34.421832,-118.595404}
我将用 PVCharacter代表用户的标注。下面就在Models群组中创建一个新类 PVCharacter ,并继承自 MKCircle.

然后用下面的代码替换 PVCharacter.h 中的内容:

#import

#import



@interface PVCharacter : MKCircle



@property (nonatomic, strong) NSString *name;

@property (nonatomic, strong) UIColor *color;



@end
新的这个类遵循 MKOverlay 协议, 并定义了两个属性: name和 color.

这个类的实现非常简单 — 不需要添加任何内容.

#import "PVCharacter.h"



@implementation PVCharacter



@end
PVParkMapViewController.m 中import PVCharacter.h。如下代码:

#import "PVCharacter.h"

现在,需要一个方法,将plist文件中的数据加载到程序中。将如下代码添加到PVParkMapViewController.m:

- (void)addCharacterLocation {

NSString *batmanFilePath = [[NSBundle mainBundle] pathForResource:@"BatmanLocations" ofType:@"plist"];

NSArray *batmanLocations = [NSArray arrayWithContentsOfFile:batmanFilePath];

CGPoint batmanPoint = CGPointFromString(batmanLocations[rand()%4]);

PVCharacter *batman = (PVCharacter *)[PVCharacter circleWithCenterCoordinate:CLLocationCoordinate2DMake(batmanPoint.x, batmanPoint.y)

radius:MAX(5, rand()%40)];

batman.color = [UIColor blueColor];



NSString *tazFilePath = [[NSBundle mainBundle] pathForResource:@"TazLocations" ofType:@"plist"];

NSArray *tazLocations = [NSArray arrayWithContentsOfFile:tazFilePath];

CGPoint tazPoint = CGPointFromString(tazLocations[rand()%4]);

PVCharacter *taz = (PVCharacter *)[PVCharacter circleWithCenterCoordinate:CLLocationCoordinate2DMake(tazPoint.x, tazPoint.y)

radius:MAX(5, rand()%40)];

taz.color = [UIColor orangeColor];



NSString *tweetyFilePath = [[NSBundle mainBundle] pathForResource:@"TweetyBirdLocations" ofType:@"plist"];

NSArray *tweetyLocations = [NSArray arrayWithContentsOfFile:tweetyFilePath];

CGPoint tweetyPoint = CGPointFromString(tweetyLocations[rand()%4]);

PVCharacter *tweety = (PVCharacter *)[PVCharacter circleWithCenterCoordinate:CLLocationCoordinate2DMake(tweetyPoint.x, tweetyPoint.y)

radius:MAX(5, rand()%40)];

tweety.color = [UIColor yellowColor];



[self.mapView addOverlay:batman];

[self.mapView addOverlay:taz];

[self.mapView addOverlay:tweety];

}
上面的这个方法对每个标注都做了相同的操作。首先,从plist中读出数据,然后随即选取出一个位置。下一步,根据选取出的位置,创建一个 PVCharacter 实例。随即选取一个半径值。



最后,给每个标注都设置一个颜色,并添加到map view中。



现在基本算是完成了 — 你还记得最后几步是如何操作的吗?



没错 — 你同样还需要通过delegate方法,给map view提供 MKOverlayView.



更新一下 PVParkMapViewController.m 中的delegate方法:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay {

if ([overlay isKindOfClass:PVParkMapOverlay.class]) {

UIImage *magicMountainImage = [UIImage imageNamed:@"overlay_park"];

PVParkMapOverlayView *overlayView = [[PVParkMapOverlayView alloc] initWithOverlay:overlay overlayImage:magicMountainImage];



return overlayView;

} else if ([overlay isKindOfClass:MKPolyline.class]) {

MKPolylineView *lineView = [[MKPolylineView alloc] initWithOverlay:overlay];

lineView.strokeColor = [UIColor greenColor];



return lineView;

} else if ([overlay isKindOfClass:MKPolygon.class]) {

MKPolygonView *polygonView = [[MKPolygonView alloc] initWithOverlay:overlay];

polygonView.strokeColor = [UIColor magentaColor];



return polygonView;

} else if ([overlay isKindOfClass:PVCharacter.class]) {

MKCircleView *circleView = [[MKCircleView alloc] initWithOverlay:overlay];

circleView.strokeColor = [(PVCharacter *)overlay color]];



return circleView;

}



return nil;

}


最后,更新一下 loadSelectedOptions ,让用户可以打开或者隐藏标注信息。

如下是更新后的代码:

- (void)loadSelectedOptions {

[self.mapView removeAnnotations:self.mapView.annotations];

[self.mapView removeOverlays:self.mapView.overlays];

for (NSNumber *option in self.selectedOptions) {

switch ([option integerValue]) {

case PVMapOverlay:

[self addOverlay];

break;

case PVMapPins:

[self addAttractionPins];

break;

case PVMapRoute:

[self addRoute];

break;

case PVMapBoundary:

[self addBoundary];

break;

case PVMapCharacterLocation:

[self addCharacterLocation];

break;

default:

break;

}

}

}
编译并运行程序,然后打开character,将看到如下内容:


何去何从?

上面的内容就是本文要介绍的了 — 至此,你已经知道如何使用MapKit绘制自己的overylay image和overlay view。


这里是本文最后完成工程代码: final example project



恭喜你 — 你已经使用了MapKit提供的许多重要的功能了。并完成了一个地图程序最基本的功能,实现了标注,卫星视图和定制图层!



这个程序的另一个方向就是研究一下创建地图图层的其它方法。



实际上,从简单到复杂的,有许多不同的方法来创建图层。本文使用的方法是图片: overlay_park .



为了生成图层,将卫星视图的截图当做公园的底层。然后绘制一些过山车,树,停车点,以及其它的一些新图层。



当获得了卫星截图后,需要给这个截图的4个角落确定一下经纬度。这些经纬度将用来创建公园的属性列表 — 用在map view上的位置图层信息。



这里还有很多高级的方法来创建图层 — 或许更加高效. 一些可选的方法是使用 KML文件、 MapBox 或者其它第三方提供的资源。



本文的主要目的是介绍关于MapKit framework APIs,因此没有深入介绍overylay类型。如果你是一名地图高级开发者,你可以更加深入的研究一下其它方面的主题。



希望本文对你有用,我也希望能够看到你在程序中使用MapKit overylay。如果你有任何问题或者建议,请反馈给我!




来源:破船的博客

原文:http://www.raywenderlich.com/30001/overlay-images-and-overlay-views-with-mapkit-tutorial
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: