天天品尝iOS7甜点 :: Day 13 :: Route Directions with MapKit

这篇文章是天天品尝iOS7甜点系列的一部分,你可以查看完整的系列目录:天天品尝iOS7甜点


Introduction - 介绍

对于iOS中的地图框架,在iOS7中添加了一些些小小的改变。一个简单的添加的例子就是我们可以通过两点来或者它们之间的路径。在今天的文章中,我们将会看看如何来使用这个简单的API构建一个一件的查询路径的应用程序。通过这个还可以看看添加层的渲染的API。

本章的实例程序能够在github上面进行访问,访问地址:github.com/ShinobiControls/iOS7-day-by-day

Requesting Directions - 查询路线请求

MapKit中,我们需要用到很多不同的类,但是我们使用的时候却是非常简单的。为了能够获取到Apple的路径集合服务,我们需要把信息封装到MKDirectionsRequest对象中。自从iOS6中就包含了此类,不过只是提高给Apple内部进行生成查询路线,但是在iOS7中已经得到了扩展,允许开发者进行请求指示。

1
MKDirectionsRequest *directionsRequest = [MKDirectionsRequest new];

为了封装一个请求,我们需要设置开始和结束的点,它们都是MKMapItem对象。这些对象可以代表地图上面的位置,包含自身的坐标和其他一些元数据(例如:名称,电话号码和URL)。创建它们有很多的选项。下面是其中一个使用用户当前的坐标的:

1
MKMapItem *source = [MKMapItem mapItemForCurrentLocation];

当用户启动第一次启动应用程序的时候,就会弹出一个询问是否使用他们当前位置权限的对话框:

你也可以使用一个指定的坐标来创建一个map item,使用这个方法initWithPlacemark:,这就需要使用MapKit中的另外的一个类.MKPlacemark代表了实际地图上面的坐标(也就是经度和纬度)。我们可以使用CoreLocation中的转换方法来生成一个placemark,但是这是超过了本篇要将的内容的范畴,我们将为一些固定的坐标创建placemark.把他们封装在一起就可以完成MKDirectionsRequest对象的设置了。

1
2
3
4
5
6
7
// Make the destination
CLLocationCoordinate2D destinationCoords = CLLocationCoordinate2DMake(38.8977, -77.0365);
MKPlacemark *destinationPlacemark = [[MKPlacemark alloc] initWithCoordinate:destinationCoords addressDictionary:nil];
MKMapItem *destination = [[MKMapItem alloc] initWithPlacemark:destinationPlacemark];
// Set the source and destination on the request
[directionsRequest setSource:source];
[directionsRequest setDestination:destination];

MKDirectionsRequest中还有一些其他的属性,它们是用来控制路径的,我们后面再来进行设置:

  • departureDatearrivalDate。设置了这些值将会返回一天中最优的交通状况的路径。例如允许标准的交通状况。
  • transportType。当前Apple提供了步行和驾车的选择使用枚举值MKDirectionsTransportTypeAutomobile或者MKDirectionsTransportTypeWalking。默认的值是MKDirectionsTransportTypeAny
  • requestsAlternateRoutes。如果查询路线的服务器查询出多于一个可选的路线,然后这是这个属性值为YES,就会返回所有路线。否则则会返回一条路线。

现在,我们已经得到一个完善的路径查询请求,我们可以发送它到服务器来获得路线。可以通过下面的代码进行发送请求:

1
MKDirections *directions = [[MKDirections alloc] initWithRequest:directionsRequest];

这里有两个方法可供使用,calculateETAWithCompletionHandler:估算获得路线的时间,calculateDirectionsWithCompletionHandler计算出实际的路线。这些方法都是异步的,并且都有一个回调函数块。MKDirections对象有一个cancel方法,用来作为当前运行请求的建议.一个MKDirections对象一次只能运行一个请求,其他附加的请求将会失败。如果你想要运行多个模拟请求,你就需要多个MKDirections对象,但是必须意识到请求很多有可能Apple服务会返回错误。

1
2
3
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
// Handle the response here
}];

Directions Response - 请求路线响应

这个从Apple服务返回的响应是一个MKDirectionsResponse对象,以及开始和结束坐标点,包含了一个MKRoute对象的数组.需要注意的是,如果我们设置requestsAlternateRoutes为YES,那么数组中就值包含一条信息。

MKRoute对象,正如名字所示,代表两个点之间的路线。它包含了一系列的路线属性信息:

  • name:道路的名称
  • advisoryNotices:包含一些警告的详细信息
  • distance:路线的长度
  • expectedTravelTime:行走花费的时间
  • transportType:行走的类型
  • polyline:MKPolyline对象代表了地图上面的路段信息,可以被用来绘制在地图视图上面
  • steps:装有MKRouteStep对象的数组,用来实现不同路段的连通。

另一个参数是NSError对象,所以我们可以使用下面的请求路线响应函数:

1
2
3
4
5
6
7
8
9
10
11
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
// Now handle the result
if (error) {
NSLog(@"There was an error getting your directions");
return;
}

// So there wasn't an error - let's plot those routes
_currentRoute = [response.routes firstObject];
[self plotRouteOnMap:_currentRoute];
}];

我们使用了一个工具的方法来实现绘制路线到地图上面,将会在下一章节介绍。

Rendering a Polyline - 绘制路线

我们已经把道路信息转换成多段线。iOS7改变了在地图上面渲染层的方法,具体的在MKOverlayRenderer类中介绍.如果你想使用自定义的图形或者不是标准的绘制技术,我们就可以继承创建一个自定的渲染类,然而,系统提供了很多的渲染层。我们希望渲染一个多段线,所以,我们可以使用MKPolylineRenderer类。我们需要确定何时何地来创建我们自己的渲染类,但是首先我们需要看看上一节中编写的plotRouteMap:方法的实现。

一个MKPolyline对象代表了一个路线的多段信息,并且适配MKOverlay协议。这就意味着,我们可以把它作为一个层添加到MKMapView对象中,通过使用addOverlay:方法来实现:

1
2
3
4
5
6
7
8
9
10
11
- (void)plotRouteOnMap:(MKRoute *)route {
if (_routeOverlay) {
[self.mapView removeOverlay:_routeOverlay];
}

// Update the ivar
_routeOverlay = route.polyline;

// Add it to the map
[self.mapView addOverlay:_routeOverlay];
}

这个方法使用了MKRoute对象,并且添加了路线中的多段线层,然后被添加到mapView中。我们使用了一个实例变量来对polyline进行全局的引用。这意味着我们可以先删除之前的层信息,然后重新赋值新的信息。

尽管我们现在已经把层添加到mapView上面了,但是它已经还没有实现绘制工作。这是因为地图就不知道如何来绘制这个层对象,所以这就是需要我们创建MKOverlayRenderer对象的时候了。当一个层被添加到mapview上面的时候,mapview就是调用它自身的代理来渲染这个层。

我们需要适配MKMapViewDelegate协议,并且实现其中的一些方法用来提供mapview渲染我们自己的多段线:

1
2
3
4
5
6
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay];
renderer.strokeColor = [UIColor redColor];
renderer.lineWidth = 4.0;
return renderer;
}

我们这里有些简化的情况是,我们知道这里只徐奥一个覆盖层,并且它的类型是MKPolyline,因此并不需要其他的代码来确定渲染器如何返回。我们创建一个MKPolylineRenderer,它是MKOverlayRenderer的子类目的是绘制多段线覆盖层。我们设置一些简单的属性(strokeColorlineWidth),所以我们可以看到创建的覆盖层,然后返回创建的渲染器。

在这之前需要先设置mapview的delegate,然后会自动调用代理的方法来把覆盖层添加到地图上面:

1
2
3
4
5
6
- (void)viewDidLoad {
[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.
self.mapView.delegate = self;
}

Route steps - 路线步骤

正如上面所述的polyline代表的是一个路线,我们还提供了包含MKRouteStep的数组对象,它表示了一个用户从起点到终点走的不用路线的信息。MKRouteStep对象有如下的属性:

  • polyline:表示路线的信息
  • instructions:告诉用户此路线应该如何行走的提示信息
  • notice:一些有关路线的信息
  • distance:路线距离
  • transportType:行走路线的方式

在今天文章中我们完成的RouteMaster应用程序,提供了一个表格用来显示行走路线步骤的列表,然后把不同路线展示在地图上面:

Building RouteMaster - 构建RouteMaster应用程序

我们现在来介绍我们使用路线请求和请求返回数据的过程,但是并不是很详细的介绍今天本文构建的应用程序,尽管它并没有很深入的介绍MapKit的细节,但快速的查看我们应用程序的构建还是值得的。

因为我们使用的storyboards,我们复写prepareForSegue:sender:方法来介绍我们的视图控制器的跳转,并且需要传递的数据。例如,SCStepsViewController有一个route属性,我们需要进行设置:

1
2
3
4
5
6
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[SCStepsViewController class]]) {
SCStepsViewController *vc = (SCStepsViewController *)segue.destinationViewController;
vc.route = _curentRoute;
}
}

同样的,SCIndividualStepViewController也有一个routeStep属性:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue destinationViewController] isKindOfClass:[SCIndividualStepViewController class]]) {
SCIndividualStepViewController *vc = (SCIndividualStepViewController *)[segue destinationViewController];
NSIndexPath *selectRow = [self.tableView indexPathForSelectedRow];

// If we have a selected row then set the step appropriately
if (selectedRow) {
vc.routeStep = self.route.steps[selectedRow.row];
vc.stepIndex = selectedRow.row;
}
}
}

应用程序在模拟器中需要获得当前的位置,所以我们可以在Xcode中的Debug->Simulate Location进行设置:

在实际的应用程序中,我们可以使用CoreLocation进行获得位置信息.

Conclusion - 总结

在iOS7中,MapKit开始成熟一点,外加一些非常有用的api。directions API相当容易使用,尽管有大量不同的类,并返回结果与在应用程序非常容易的工作。我们现在需要的是不断提高苹果的后端服务,以便我们提供给用户更加合理的结果。

本文翻译自:iOS7 Day-by-Day :: Day 13 :: Route Directions with MapKit

文章目录
  1. 1. Introduction - 介绍
  2. 2. Requesting Directions - 查询路线请求
  3. 3. Directions Response - 请求路线响应
  4. 4. Rendering a Polyline - 绘制路线
  5. 5. Route steps - 路线步骤
  6. 6. Building RouteMaster - 构建RouteMaster应用程序
  7. 7. Conclusion - 总结
,