iOS-raywenderlich翻译-使用MapKit叠加图层

注:本文由BeyondVincent(破船)翻译首发

        转载请注明出处:http://blog.csdn.net/beyondvincent

 

 

 

5 MARCH 2013
 
本文是由iOS Tutorial小组成员Chris Wagner撰写,他是一名软件工程爱好者,一直在努力做一名前沿开发者。
 
使用MapKit在程序中添加一个地图是非常容易的事。不过,如果你希望使用自己的注解和图片来装饰或者定制苹果提供的地图呢?
 
非常幸运的是苹果提供了非常容易的方法来完成这样的需求:自定义叠加图层。
 
在本文中,你将为六七魔术山(Six Flags Magic Mountain)游乐园创建一个程序。如果你在洛杉矶是一个做过山车的粉丝,你会喜欢这个程序的 :]
 
想想,一个来到游乐园的游人都会喜欢这个程序的一些功能:例如具体景点的位置,各种过山车的路线,以及公园的一些特点。这些内容的显示通过定制一个叠加图片是非常好的选择——这真是本文要介绍的内容。
学习如何使用MapKit在地图上添加一个叠加图层

注意: 根据你的经验水平,学习本文,你有两种选择:

  1. 对MapKit已经熟悉了? 如果你对MapKit已经熟悉了,并且你想要马上学习叠加图层的内容,你可以忽略(或略读)掉前面的内容,直接跳到“What a View”小节—在这里我为你准备了一个启动项目。
  2. 对MapKit还是一个新手?如果你对MapKit还一无所知,那么请继续往下阅读,我将从最基础的内容开始在程序中添加一个地图!

苹果地图 VS Google 地图

Apple Maps no longer uses Google Maps!

在开始编码之前,我先来说一下关于苹果地图和Google地图的争议。

在iOS开始之初,苹果就提供了一个地图程序,这个地图程序的数据最初是由Google地图 API提供的。而在iOS 6中一切都改变了,苹果打破了与Google之间的关系,发布了自己的地图程序,并且后端数据是由苹果自己提供的。

这对于博客、媒体、用户,甚至你的妈妈都是一个热议话题。有些说苹果已经完成了一个难以置信的工作,并且放弃Google,而选择自身作为地图提供者是一个正确的选择。而有些人则持想法的态度,他们认为这是苹果自从iPhone在2007问世以来,做的最糟糕的一个决定。

现在,如果你使用MapKit那么是在使用苹果地图。如果以前使用过MapKit,你会发现两个版本的API非常相似。

无论你的位置在哪里,在地图上总会有空间来展现更多的信息!因此,本文中你将学到如何使用苹果流行(无论是有名或者臭名昭著)的地图并添加你自己的相关信息。

 

 

 

入门

 

为了开始学习,先下载starter project,这个工程提供了基本的一个程序,可以在iPhone和iPad上运行,工程里面有一些基本的导航—但是还没有地图!

在starter project中提供的界面包含一个UISegmentedControl控件,用来切换不同的地图类型(稍后即将实现),此外还有有个动作按钮—用来显示一个table画面(里面是一些选项列表),通过这个table中的选项可以控制那种地图特征会被显示出来。通过轻击table中的选项就可以对选项选中或者取消选中。然后轻击Done按钮就可以把这些选项列表隐藏掉。

PVMapOptionsViewController负责管理选项视图,稍后你将看到,在里面定义了一个非常重要的enum。这个类中剩余的代码则超出了本文的介绍范围。不过,如果你希望了解更多关于UITableView的知识,那么可以看一下这里的内容:UITableView tutorials 

在Xcode中打开这个starter project,编译并运行。我敢打赌,你对目前这个工程有点失望,因为你将看到的如下内容:

starter project非常的简单!如果你希望地图程序能做任何有用的事情,那么你需要为这个工程添加一个地图!

 

 

你知道去San Jose的道路吗?—添加一个MapView

为了在程序中添加一个MapView,首先请打开MainStoryboard_iPhone.storyboard文件。选择Park Map View Controller,然后将一个Map View对象拖拽到view中,调整MapView以填满整个view,如下图所示:

 

 

现在打开MainStoryboard_iPad.storyboard文件,跟上面的操作步骤一样,添加一个MapView,然后调整一下这个MapView的位置以填满整个view。

现在你如果编译并运行程序的话,程序会crash掉,并且提示如下信息:

*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named MKMapView'

这是因为你还没有把MapKit.framework链接到你的target中!

为了将其链接到target中,在工程导航栏中选择Park View工程,然后选中Park View target。下一步打开Build Phases选项,然后在Link Library With Binaries下面单击+按钮,如下图所示:

在弹出的窗口中搜索MapKit,选中它,然后单击Add将其添加到工程中,如下截图所示:

现在编译并运行程序,可以看到新的地图了!看起来如下截图所示:

如上所示,在程序中添加一个地图并不需要做太多的工作。

在程序里面有一个地图是非常cool的,如果能让地图做一些实际的事情会更cool!:]下一节中,将介绍如何在程序中获得这个MapView,以进行交互。

又长又曲折的道路—连接到你的MapView

 

要想用MapView做任何事情,你需要做两件事情—将其与一个outlet关联,将view controller注册为MapView的delegate。

但是首先你需要import MapKit头文件。打开PVParkMapViewController.h 并将下面的代码添加到文件的顶部:

#import <MapKit/MapKit.h>

下一步,打开MainStoryboard_iPhone.storyboard 文件,并将Assistant Editor打开,让PVParkMapViewController.h 可见.。然后从map view control-drag到下面的第一个属性,如下图所示:

Add MapView Outlet

在弹出的画面中,将outlet命名为mapView,,然后单击Connect。

现在你需要为MapView设置delegate。这样做:在MapView上右键单击,会弹出一个context菜单,然后将delegate连接到Map View Controller上,如下图所示:

现在对iPad storyboard做相同的操作 — 将MapView连接到mapView插槽中(这次只需要将其拖拽到已经存在的插槽上即可,不需要创建一个新的),并将view controller设置为MapView的delegate。

现在已经完成了插槽的连接,下面你还需要修改一下PVParkMapViewController头文件的接口声明,让其遵循MKMapViewDelegate协议。

最终PVParkMapViewController.h中的接口声明如下所示:

@interface PVParkMapViewController : UIViewController <MKMapViewDelegate>

通过上面的操作,我们完成了插槽,delegate,controller的配置。现在可以在地图中添加一些交互了!

 

我在这里,你不知道如何从这而到那儿 – 与 MKMapView进行交互

 

虽然地图默认的视图非常好看,但是这对于只关注主题公园(而不是所有的大陆)的人来说,使用起来太广泛了!当程序启动的时候,将公园的地图视图放置在程序的中间非常好。

获得某个具体位置的位置信息有许多中方法;可以通过web service获取,也可以将位置信息内置在程序中。

为了简单起见,在本文中,我把公园的位置信息打包放在程序中。下载这个工程的资源(resources for this project),里面有一个名为MagicMountain.plist的文件,包含了公园的信息。

MagicMountain.plist的内容如下:

 

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
  3. <plist version="1.0">  
  4. <dict>  
  5.         <key>midCoord</key>  
  6.         <string>{34.4248,-118.5971}</string>  
  7.         <key>overlayTopLeftCoord</key>  
  8.         <string>{34.4311,-118.6012}</string>  
  9.         <key>overlayTopRightCoord</key>  
  10.         <string>{34.4311,-118.5912}</string>  
  11.         <key>overlayBottomLeftCoord</key>  
  12.         <string>{34.4194,-118.6012}</string>  
  13.         <key>boundary</key>  
  14.         <array>  
  15.                 <string>{34.4313,-118.59890}</string>  
  16.                 <string>{34.4274,-118.60246}</string>  
  17.                 <string>{34.4268,-118.60181}</string>  
  18.                 <string>{34.4202,-118.6004}</string>  
  19.                 <string>{34.42013,-118.59239}</string>  
  20.                 <string>{34.42049,-118.59051}</string>  
  21.                 <string>{34.42305,-118.59276}</string>  
  22.                 <string>{34.42557,-118.59289}</string>  
  23.                 <string>{34.42739,-118.59171}</string>  
  24.         </array>  
  25. </dict>  
  26. </plist>  


 

上面的文件中包含的信息不仅有你现在需要的(将公园放在地图中间);另外,还包含了公园的边界信息—稍后会用到。

文件中所有的信息都是以经度/维度坐标(latitude/longitude coordinates)格式提供的。

把这个文件添加到工程中:通过把这个文件拖拽工程的Park Informatio群组中。

现在你已经有了关于公园的地理位置信息了,下面你应该把这些地理信息模型化为Objective-C对象,以便于在程序中使用。

选择工程中的Models群组,然后选择File > New > File… > Objective-C Class (在Cocoa Touch里面)。将类命名为PVPark,并继承自NSObject。

当新的类创建好之后,将下面的属性和初始化方法添加到PVPark.h:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface PVPark : NSObject
 
@property (nonatomic, readonly) CLLocationCoordinate2D *boundary;
@property (nonatomic, readonly) NSInteger boundaryPointsCount;
 
@property (nonatomic, readonly) CLLocationCoordinate2D midCoordinate;
@property (nonatomic, readonly) CLLocationCoordinate2D overlayTopLeftCoordinate;
@property (nonatomic, readonly) CLLocationCoordinate2D overlayTopRightCoordinate;
@property (nonatomic, readonly) CLLocationCoordinate2D overlayBottomLeftCoordinate;
@property (nonatomic, readonly) CLLocationCoordinate2D overlayBottomRightCoordinate;
 
@property (nonatomic, readonly) MKMapRect overlayBoundingMapRect;
 
@property (nonatomic, strong) NSString *name;
 
- (instancetype)initWithFilename:(NSString *)filename;
 
@end

这里的许多属性看起来非常相似,它们将引用到上面的plist文件中。

注意初始化方法initWithFileName,需要给这个方法传递包含坐标信息的一个plist文件,以对这个对象进行初始化。

注意:
你可能已经注意到这个初始化方法返回的类型是instancetype,而不是id。 这是LLVM编译中相对教新的内容。更多相关内容可以参考NSHipster

现在是时候来实现PVPark.m.文件了。这里将添加两个方法。第一个是initWithFileName, 将plist文件中的所有信息读取到头文件中定义好的属性中。如果你做过文件I/O操作,那么这部分将非常的简单。

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

- (instancetype)initWithFilename:(NSString *)filename {
    self = [super init];
    if (self) {
        NSString *filePath = [[NSBundle mainBundle] pathForResource:filename ofType:@"plist"];
        NSDictionary *properties = [NSDictionary dictionaryWithContentsOfFile:filePath];
 
        CGPoint midPoint = CGPointFromString(properties[@"midCoord"]);
        _midCoordinate = CLLocationCoordinate2DMake(midPoint.x, midPoint.y);
 
        CGPoint overlayTopLeftPoint = CGPointFromString(properties[@"overlayTopLeftCoord"]);
        _overlayTopLeftCoordinate = CLLocationCoordinate2DMake(overlayTopLeftPoint.x, overlayTopLeftPoint.y);
 
        CGPoint overlayTopRightPoint = CGPointFromString(properties[@"overlayTopRightCoord"]);
        _overlayTopRightCoordinate = CLLocationCoordinate2DMake(overlayTopRightPoint.x, overlayTopRightPoint.y);
 
        CGPoint overlayBottomLeftPoint = CGPointFromString(properties[@"overlayBottomLeftCoord"]);
        _overlayBottomLeftCoordinate = CLLocationCoordinate2DMake(overlayBottomLeftPoint.x, overlayBottomLeftPoint.y);
 
        NSArray *boundaryPoints = properties[@"boundary"];
 
        _boundaryPointsCount = boundaryPoints.count;
 
        _boundary = malloc(sizeof(CLLocationCoordinate2D)*_boundaryPointsCount);
 
        for(int i = 0; i < _boundaryPointsCount; i++) {
            CGPoint p = CGPointFromString(boundaryPoints[i]);
            _boundary[i] = CLLocationCoordinate2DMake(p.x,p.y);
        }
    }
 
    return self;
}

在上面的代码中,CLLocationCoordinate2DMake() 利用经度和维度构建一个CLLocationCoordinate2D结构。在MapKit中CLLocationCoordinate2D 结构代表了一个地理位置。

在初始方法中还创建了一个CLLocationCoordinate2D 数组,并将数组的指针设置给_boundary。这非常重要:之后需要将这样的一个指针传递到CLLocationCoordinate2D 结构的数组中。

有一个属性你可能已经注意到我们并没有对其进行初始化—overlayBottomRightCoordinate。而其它三个角(右上、左上和左下)我们都提供了值,但是为什么右下没有提供呢?

原因是通过其它三个点可以计算出最后的这个点—如果对于一个可以计算出来的值,在提供的话就显得有点多余了。

为了计算出右下角的坐标,将下面的代码添加到PVPark.m中:

- (CLLocationCoordinate2D)overlayBottomRightCoordinate {
    return CLLocationCoordinate2DMake(self.overlayBottomLeftCoordinate.latitude, self.overlayTopRightCoordinate.longitude);
}

这个方法使用左下和右上坐标可以计算出右下坐标,该方法扮演了getter方法。

最后,你需要根据上面读取出来的坐标创建一个边界框。

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

 

- (MKMapRect)overlayBoundingMapRect {
 
    MKMapPoint topLeft = MKMapPointForCoordinate(self.overlayTopLeftCoordinate);
    MKMapPoint topRight = MKMapPointForCoordinate(self.overlayTopRightCoordinate);
    MKMapPoint bottomLeft = MKMapPointForCoordinate(self.overlayBottomLeftCoordinate);
 
    return MKMapRectMake(topLeft.x,
                  topLeft.y,
                  fabs(topLeft.x - topRight.x),
                  fabs(topLeft.y - bottomLeft.y));
}

这个方法将构造出一个MKMapRect ,代表了公园的边界框。它真的只是一个矩形框,用来表示公园有多大(利用上面提供的坐标),并且是集中在公园的中心位置。

现在时时候使用这个新创建的类了。更新一下PVParkMapViewController.m 文件:

import PVPark.h 并在类扩展中添加一个park 属性:

#import "PVPark.h"
 
@interface PVParkMapViewController ()
 
@property (nonatomic, strong) PVPark *park;
@property (nonatomic, strong) NSMutableArray *selectedOptions;
 
@end

然后将下面的代码添加到viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    self.selectedOptions = [NSMutableArray array];
    self.park = [[PVPark alloc] initWithFilename:@"MagicMountain"];
 
 
    CLLocationDegrees latDelta = self.park.overlayTopLeftCoordinate.latitude - self.park.overlayBottomRightCoordinate.latitude;
 
    // think of a span as a tv size, measure from one corner to another
    MKCoordinateSpan span = MKCoordinateSpanMake(fabsf(latDelta), 0.0);
 
    MKCoordinateRegion region = MKCoordinateRegionMake(self.park.midCoordinate, span);
 
    self.mapView.region = region;    
}

上面的代码使用MagicMountain 属性列表来初始化 park 属性。接着创建了一个维度增量—这个距离表示从park属性的左上坐标到右下坐标之间的距离。

通过利用维度增量来生成一个 MKCoordinateSpan 结构, 这个结构定义了地图区域的跨度。

然后通过MKCoordinateSpan和 midCoordinate 属性 (就是公园边界区域的中心) 来创建一个 MKCoordinateRegion

然后将MKCoordinateRegion 结构赋值给map view的region属性,用来定位地图的位置。

编译并运行程序,可以看到地图显示的内容是六七魔术山公园的正中心。如下图所示:

Ok! 现在地图已经显示出了公园的正中心,非常帮!但是显示的内容并不能让我们非常兴奋。它只是显示了几个米色空白区域,外加边缘上有几条街道。

如果你以前使用过地图程序,你肯定知道里面的卫星地图看起来非常cool。其实你也可以很容易就能够在程序中使用卫星地图数据!

 

伙计,我到处都去过啦 – 切换地图的类型

 

在 PVParkMapViewController.m中的最下面,你可以看到这样一个方法:

- (IBAction)mapTypeChanged:(id)sender {
    // TODO: Implement
}

代码中的注释内容是 TODO ! :]

在这个方法中需要写一些代码哦。你注意到map view上面的UISegmentedControl,这个UISegmentedControl实际上会调用 mapTypeChanged, 不过,在上面的方法中还没有任何实现!

将下面的代码添加到 mapTypeChanged 方法中:

- (IBAction)mapTypeChanged:(id)sender {
    switch (self.mapTypeSegmentedControl.selectedSegmentIndex) {
        case 0:
            self.mapView.mapType = MKMapTypeStandard;
            break;
        case 1:
            self.mapView.mapType = MKMapTypeHybrid;
            break;
        case 2:
            self.mapView.mapType = MKMapTypeSatellite;
            break;
        default:
            break;
    }
}

或许你会难以执行—在程序中添加标准、卫星和混合地图类型是如此的简单—如上代码,只需要根据 mapTypeSegmentedControl.selectedSegmentIndex的索引值进行切换即可。很简单吧?

编译并运行程序,通过屏幕顶部的UISegmentedControl,你就可以切换地图的类型了,如下所示:

虽然卫星视图比标准视图内容更丰富,但是它对于公园的有人仍然不够用。地图里面没有任何的提示 — 你的用户如何找到公园里面的内容呢?

一个很实用的方法就是在MapView上放置一个UIView,不过你可以使用更好的一个方法 — 通过 MKOverlayView 来做相关的处理!

是什么样的一个View – 关于Overlay View

注意: 如果你跳过了本文前面部分的内容,你可以在这里下载到项目代码(this starter project) 同时你还需要下载项目的资源文件(resources for this project),并将其添加到项目中。

在开始创建自己的view之前,我们先来看两个类 –MKOverlay 和 MKOverlayView.

MKOverlay这个类会告诉MapKitis你想在哪里绘制叠加图层。使用它,有三个步骤:

  1. 创建一个你自己的类,并实现 MKOverlay 协议, 这个协议有两个required属性: coordinate 和 boundingMapRect。 这两个属性定义了叠加图层在地图中的位置,以及图层的大小。
  2. 在这个类中为希望显示的每个区域创建一个实例,在程序中,可以创建一个过山车图层和餐厅图层。
  3. 最后,通过调用下面的代码,将图层添加到你的Map View中:
[self.mapView addOverlay:overlay];

现在MapView以及知道将图层显示到什么地方了 — 但是,它是如何知道每个区域显示的内容呢?

进入 MKOverlayView. 你创建一个该类的子类,并设置一下你想要显示的内容。在这里的程序中,你只需要绘制一个关于过山车或餐厅的图片即可。

MKOverlayView 实际上是继承自UIView的。不过MKOverlayView 是特殊的UIView,你不能将其直接添加到MKMapView中。这个view是MapKit框架希望你提供的。你提供了这个view之后,这个view将以图层的方式渲染在地图的上方。 

还记得如何给MapView设置delegate - 以及在本文中将view controller设置为它的delegate吗?OK,你实现的有个delegate方法会返回一个叠加图层:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay

当map view发现在其显示区域内有一个 MKOverlay 对象时,会调用上面这个方法。

概括的说,你不要直接将MKOverlayView添加到map view中; 而是告诉map关于MKOverlays如何显示,以及在delegate方法中需要的时候再将其。

上面就是涉及到的理论知识,是时候结合这些理论来进行编码了!

将你自己放置到地图中 – 添加你自己的信息

如之前看到的,卫星视图仍然不能提供关于公园的更多信息。现在,你的任务就是创建一个对象,该对象代表着整个公园的一个图层(对公园进行一些装饰)。

选中 Overlays 群组,然后创建一个继承自 NSObject 的类,名字为PVParkMapOverlay. 然后用下面的代码替换 PVParkMapOverlay.h 中的代码:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@class PVPark;
 
@interface PVParkMapOverlay : NSObject <MKOverlay>
 
- (instancetype)initWithPark:(PVPark *)park;
 
@end

在上面的代码中,导入了MapKit 头文件, 并添加了一个 PVPark 前向声明, 并告诉编译器,该类遵循 MKOverlay 协议. 最后,定义了方法 initWithPark.

下一步,用下面的代码替换 PVParkMapOverlay.m 中的代码:

#import "PVParkMapOverlay.h"
#import "PVPark.h"
 
@implementation PVParkMapOverlay
 
@synthesize coordinate;
@synthesize boundingMapRect;
 
- (instancetype)initWithPark:(PVPark *)park {
    self = [super init];
    if (self) {
        boundingMapRect = park.overlayBoundingMapRect;
        coordinate = park.midCoordinate;
    }
 
    return self;
}
 
@end

在上面,导入了 PVPark 头文件. 然后实现一下协议中定义的的 coordinate 和boundingMapRect 两个属性, 在这里必须明确的将其@synthesize. 接着实现 initWithPark 方法. 这个方法从传入的 PVPark 对象获取出相关属性,并将其赋值给相应的 MKOverlay 属性.

现在你需要创建一个新的view,这个view继承自 MKOverlayView 类.

在 Overlays 群组中创建一个新的类,叫 PVParkMapOverlayView ,该类继承自MKOverlayView.

下面是PVParkMapOverlayView.h的代码,定义了一个方法:

#import <MapKit/MapKit.h>
 
@interface PVParkMapOverlayView : MKOverlayView
 
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage;
 
@end

PVParkMapOverlayView 的实现包含两个方法,并在类扩展中有一个UIImage属性。

下一步,将下面代码添加到PVParkMapOverlayView.m:

#import "PVParkMapOverlayView.h"
 
@interface PVParkMapOverlayView ()
 
@property (nonatomic, strong) UIImage *overlayImage;
 
@end
 
@implementation PVParkMapOverlayView
 
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage {
    self = [super initWithOverlay:overlay];
    if (self) {
        _overlayImage = overlayImage;
    }
 
    return self;
}
 
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
    CGImageRef imageReference = self.overlayImage.CGImage;
 
    MKMapRect theMapRect = self.overlay.boundingMapRect;
    CGRect theRect = [self rectForMapRect:theMapRect];
 
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextTranslateCTM(context, 0.0, -theRect.size.height);
    CGContextDrawImage(context, theRect, imageReference);
}
 
@end

OK, 我们来看看上面的代码.

initWithOverlay:overlayImage override了基类中的方法 initWithOverlay (第二个参数的类型是overlayImage.)。传递过来的图片存储在类扩展中的属性中,该属性在下面这个方法中会被使用:

- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context

这个方法才是这个类的重点;它定义了如何渲染这个view,根据给定的参数: MKMapRect, MKZoomScale, 和图像上下文中,以适当的比例绘制叠加图层。

关于CoreGraphics的绘制超出了本文的范围。不过,从上面的代码中,可以看出使用传入的MKMapRect 可以获得一个 CGRect, 这样就可以确定UIImage的CGImageRef绘制到上下文中的位置。如果你想了解更多关于Core Graphics的内容,请看 Core Graphics tutorial series.

OK!现在你已经有两个类了:MKOverlay 和 MKOverlayView, 你可以将他们添加到map view中.

在 PVParkMapViewController.m 文件中导入这两个新创建的类:

#import "PVParkMapOverlayView.h"
#import "PVParkMapOverlay.h"

下一步,添加下面的代码,以定义一个新的方法,并把 MKOverlay 添加到map view中:

- (void)addOverlay {
    PVParkMapOverlay *overlay = [[PVParkMapOverlay alloc] initWithPark:self.park];
    [self.mapView addOverlay:overlay];
}

addOverlay 方法应该在 loadSelectedOptions 中被调用(如果用户决定显示这个图层).

按照下面的代码更新一下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;
            default:
                break;
        }
    }
}

loadSelectedOptions 每当用户隐藏掉options selection view时,都会调用方法loadSelectedOptions; 该方法会判断出哪个选项被选中,以调用适当的方法来渲染map view。

loadSelectedOptions 还会移除对应的注解和图层,以避免出现重复渲染的效果。这里的方法可能不是高效的,但对于本课程的介绍来将是一个简单的方法。

为了实现delegate方法,添加如下代码(还是在PVParkMapViewController.m中):

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay {
    if ([overlay isKindOfClass:PVParkMapOverlay.class]) {
        UIImage *magicMountainImage = [UIImage imageNamed:@"overlay_park"];
        PVParkMapOverlayView *overlayView = [[PVParkMapOverlayView alloc] initWithOverlay:overlay overlayImage:magicMountainImage];
 
        return overlayView;
    }
 
    return nil;
}

MKOverlay需要被添加到view中时,map view会调用delegate( PVParkMapViewController )的上面这个方法。这个方法会返回与MKOverlay相匹配的一个 MKOverlayView .

在这里,检查一下看看 overlay 的类型是不是 PVParkMapOverlay; 如果是的话,就加载overlay图片,并用这个图片创建一个 PVParkMapOverlayView 实例, 然后将这个实例返回给调用者。

这个PNG文件定义了公园的范围。overlay_park 图片 (来自 resources for this tutorial) 如下所示:

在Images群组中,添加non-retina和retina图片。

编译并运行程序,选择Map Overylay选项,然后看看!这个公园的overlay已经绘制到地图上方了,如下截图所示:

你可以随心所欲的放大、缩小或移动地图——overlay会按比例进行移动与显示。Cool!

如果你喜欢的话那就在地图上放置一个Pin — 注解

如果你用Maps程序搜索过位置信息,那么你肯定看到过在地图上出现的许多Pin。这可以理解为注解(annotation),它是用 MKAnnotationView创建的。你也可以在你的程序中使用注解 — 并使用你想要的任何图片,不仅仅是pin!

在程序中使用注解来标出具体的某个景点,这对游客来说非常有用。注解对象的使用方法跟MKOverlay 和 MKOverlayView非常类似, 只不过需要使用的类是MKAnnotation 和 MKAnnotationView.

在Annotations群组中创建一个名为 PVAttractionAnnotation 新的类,并继承自 NSObject.

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

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
typedef NS_ENUM(NSInteger, PVAttractionType) {
    PVAttractionDefault = 0,
    PVAttractionRide,
    PVAttractionFood,
    PVAttractionFirstAid
};
 
@interface PVAttractionAnnotation : NSObject <MKAnnotation>
 
@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 "PVAttractionAnnotation.h"
 
@implementation PVAttractionAnnotation
 
@end

这可能是本文中最简单的实现了!在里面不需要实现任何内容;只需要了解在头文件定义的一些属性即可!

现在需要创建一个MKAnnotation 实例来使用你的注解了。

在Annotation群组中创建另外一个类:PVAttractionAnnotationView 继承自MKAnnotationView. 头文件中不需要添加任何内容。

用下面的代码替换PVAttractionAnnotationView.h 中的内容:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface PVAttractionAnnotationView : MKAnnotationView
 
@end

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

#import "PVAttractionAnnotationView.h"
#import "PVAttractionAnnotation.h"
 
@implementation PVAttractionAnnotationView
 
- (id)initWithAnnotation:(id<MKAnnotation>)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文件中包含了坐标信息以及其它与公园景点相关的一些详细信息,如下所示:

 

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
  3. <plist version="1.0">  
  4. <array>  
  5.         <dict>  
  6.                 <key>name</key>  
  7.                 <string>Goliath</string>  
  8.                 <key>location</key>  
  9.                 <string>{34.42635,-118.59712}</string>  
  10.                 <key>type</key>  
  11.                 <string>1</string>  
  12.                 <key>subtitle</key>  
  13.                 <string>Intensity: 8/10</string>  
  14.         </dict>  
  15.         <dict>  
  16.                 <key>name</key>  
  17.                 <string>Batman</string>  
  18.                 <key>location</key>  
  19.                 <string>{34.42581,-118.60089}</string>  
  20.                 <key>type</key>  
  21.                 <string>1</string>  
  22.                 <key>subtitle</key>  
  23.                 <string>Intensity: 6/10</string>  
  24.         </dict>  
  25.         <dict>  
  26.                 <key>name</key>  
  27.                 <string>Ridler's Revenge</string>  
  28.                 <key>location</key>  
  29.                 <string>{34.42430,-118.60074}</string>  
  30.                 <key>type</key>  
  31.                 <string>1</string>  
  32.                 <key>subtitle</key>  
  33.                 <string>Intensity: 6/10</string>  
  34.         </dict>  
  35.         <dict>  
  36.                 <key>name</key>  
  37.                 <string>X2</string>  
  38.                 <key>location</key>  
  39.                 <string>{34.42156,-118.59556}</string>  
  40.                 <key>type</key>  
  41.                 <string>1</string>  
  42.                 <key>subtitle</key>  
  43.                 <string>Intensity: 10/10</string>  
  44.         </dict>  
  45.         <dict>  
  46.                 <key>name</key>  
  47.                 <string>Tatsu</string>  
  48.                 <key>location</key>  
  49.                 <string>{34.42150,-118.59741}</string>  
  50.                 <key>type</key>  
  51.                 <string>1</string>  
  52.                 <key>subtitle</key>  
  53.                 <string>Intensity: 7/10</string>  
  54.         </dict>  
  55.         <dict>  
  56.                 <key>name</key>  
  57.                 <string>Panda Express</string>  
  58.                 <key>location</key>  
  59.                 <string>{34.42126,-118.595637}</string>  
  60.                 <key>type</key>  
  61.                 <string>2</string>  
  62.                 <key>subtitle</key>  
  63.                 <string>Cost: $$</string>  
  64.         </dict>  
  65.         <dict>  
  66.                 <key>name</key>  
  67.                 <string>Cold Stone</string>  
  68.                 <key>location</key>  
  69.                 <string>{34.42401,-118.59495}</string>  
  70.                 <key>type</key>  
  71.                 <string>2</string>  
  72.                 <key>subtitle</key>  
  73.                 <string>Cost: $</string>  
  74.         </dict>  
  75.         <dict>  
  76.                 <key>name</key>  
  77.                 <string>First Aid</string>  
  78.                 <key>location</key>  
  79.                 <string>{34.42640,-118.59918}</string>  
  80.                 <key>type</key>  
  81.                 <string>3</string>  
  82.                 <key>subtitle</key>  
  83.                 <string>Call 911 For Emergency</string>  
  84.         </dict>  
  85. </array>  
  86. </plist>  


 

 

现在你以及拥有上面这些资源了,当然你也可以使用新的注解!

回到 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<MKAnnotation>)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提供的MKPolylineMKPolygon 和 MKCircle 就可以做到.

我走的线路 – MKPolyline

如果你去过魔山,你应该知道 Goliath hypercoaster 是一个非常令人难以置信的旅程, 当骑手们跨进了大门,就想一路狂奔!:]

为了帮助这些骑手们,你绘制了一条从公园入口到Goliath的路径。

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

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

这个plist文件中的内容如下:

 

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
  3. <plist version="1.0">  
  4. <array>  
  5.     <string>{34.42367,-118.594836}</string>  
  6.     <string>{34.423597,-118.595205}</string>  
  7.     <string>{34.423004,-118.59537}</string>  
  8.     <string>{34.423044,-118.595806}</string>  
  9.     <string>{34.423419,-118.596126}</string>  
  10.     <string>{34.423569,-118.596229}</string>  
  11.     <string>{34.42382,-118.596192}</string>  
  12.     <string>{34.42407,-118.596283}</string>  
  13.     <string>{34.424323,-118.596534}</string>  
  14.     <string>{34.42464,-118.596858}</string>  
  15.     <string>{34.42501,-118.596838}</string>  
  16.     <string>{34.42537,-118.596688}</string>  
  17.     <string>{34.425690,-118.596683}</string>  
  18.     <string>{34.42593,-118.596806}</string>  
  19.     <string>{34.42608,-118.597101}</string>  
  20.     <string>{34.42634,-118.597094}</string>  
  21. </array>  
  22. </plist>  


 

 

如上,属性列表是一个简单的字符串数组,字符串中包含了路径中每个点的经度和纬度信息。

现在,你需要写一个方法来读取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<MKOverlay>)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<MKOverlay>)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 包含了相关标注的位置信息,只需要将其添加到工程中。

每个文件包含了一些坐标信息。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
        <string>{34.42481,-118.596914}</string>
        <string>{34.423383,-118.596101}</string>
        <string>{34.423628,-118.595197}</string>
        <string>{34.421832,-118.595404}</string>
</array>
</plist>

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

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

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface PVCharacter : MKCircle <MKOverlay>
 
@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<MKOverlay>)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 tiles,或者其它第三方提供的资源。

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

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

posted @ 2013-03-31 23:17  5101052  阅读(553)  评论(0编辑  收藏  举报