iOS 9: Day by Day 第十天 MapKit Tra
本文翻译自Chris Grant的《iOS9 Day-by-Day :: Day 10 :: MapKit Transit》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day10-mapkit-transit)。感谢Chris Grant的辛苦工作!
MapKit的每一次迭代都会引入一些新特性,iOS 9也不例外。在本文中,我们将了解一下iOS 9为我们准备的新API,并在应用中与新的ETA(预计到达时间)功能一起使用。
重要的新API
MapKit视图的改进
给地图的弹出视图增加了更多高级特性。MKAnnotation
有一下属性可以进行定制:
- 标题
- 子标题
- 右辅助视图
- 左辅助视图
- 详情弹出视图
iOS 9新增详情弹出视图,允许我们为标准的弹出视图指定详细信息。该视图完整支持自动布局和约束,可以用于自定义已有的弹出菜单。
下面是MKMapView
的一些新增属性:
showsTraffic
showsScale
showsCompass
交通运输功能改进
iOS 9引入了一个新的MKDirectionsTransportType
类型,名为MKDirectionsTransitType
。该类型只能用于ETA请求。当我们使用calculateETAWithCompletionHandler
请求一个ETA信息时,可以在完成时的处理块(Block)中获取到响应对象(MKETAResponse
)。该对象包含了预计旅行时间、距离、预计到达时间以及预计起飞时间。
创建一个示例应用
为了演示这些API的用法,并且测试一下ETA请求功能。我们将创建以下APP,它能够显示从选中位置到伦敦的各地标建筑的交通信息。
首先在故事板(Storyboard)上设置一个MKMapView
、一个UITableView
以及它们所需的约束条件。在完成上面的事情后,给UITableView
添加一个Cell原型。我们不会很深入的关注UI,只要确认ViewController
是表格视图的UITableViewDataSource
和地图的MKMapViewDelegate
。整个界面看起来如下:
我们需要创建一个自定义的Cell。它拥有一些连接Storyboard上Cell原型上的IBOutlet属性。
class DestinationTableViewCell: UITableViewCell {
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var etaLabel: UILabel!
@IBOutlet weak var departureTimeLabel: UILabel!
@IBOutlet weak var arrivalTimeLabel: UILabel!
@IBOutlet weak var distanceLabel: UILabel!
}
设置好Storyboard后,下面开始给地图添加大头针。创建一个“Destination”类来存储位置信息。
class Destination {
let coordinate:CLLocationCoordinate2D
private var addressDictionary:[String : AnyObject]
let name:String
init(withName placeName: String,
latitude: CLLocationDegrees,
longitude: CLLocationDegrees,
address:[String:AnyObject])
{
name = placeName
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
addressDictionary = address
}
}
创建位置对象:
let stPauls = Destination(
withName: "St Paul's Cathedral",
latitude: 51.5138244,
longitude: -0.0983483,
address: [
CNPostalAddressStreetKey:"St. Paul's Churchyard",
CNPostalAddressCityKey:"London",
CNPostalAddressPostalCodeKey:"EC4M 8AD",
CNPostalAddressCountryKey:"England"])
创建多个位置对象,并将它们存储在一个数组中,方便后面在地图上进行显示。
在ViewController
的viewDidLoad
方法里添加一下代码来将目标地址在地图上标记出来。
for destination in destinations {
let annotation = MKPointAnnotation()
annotation.coordinate = destination.coordinate
mapView.addAnnotation(annotation)
}
我们同样可以设置地图的初始化显示区域。
mapView.region = MKCoordinateRegion(
center: CLLocationCoordinate2D(
latitude: CLLocationDegrees(51.5074157),
longitude: CLLocationDegrees(-0.1201011)),
span: MKCoordinateSpan(
latitudeDelta: CLLocationDegrees(0.025),
longitudeDelta: CLLocationDegrees(0.025)))
下一步,在表格上显示目标地址:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return destinations.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("destinationCell") as! DestinationTableViewCell
cell.destination = destinations[indexPath.row]
return cell
}
运行程序就可以看到地图上标记了所有的目标地址,而表格里显示这写位置的地名。
现在我们还不能计算方向,因为还没有标记起始位置。虽然可以使用用户当前的位置,但理想的是让用户自己在地图上进行点击选择。下面给地图添加点击手势:
let tap = UITapGestureRecognizer(target: self, action: "handleTap:")
mapView.addGestureRecognizer(tap)
处理点击手势,并将视图坐标转换为地理坐标:
let point = gestureRecognizer.locationInView(mapView)
userCoordinate = mapView.convertPoint(point, toCoordinateFromView:mapView)
一旦存储了用户的位置坐标后,添加一个大头针来表示,并且记得移除之前放置的大头针。
if userAnnotation != nil {
mapView.removeAnnotation(userAnnotation!)
}
userAnnotation = MKPointAnnotation()
userAnnotation!.coordinate = userCoordinate!
mapView.addAnnotation(userAnnotation!)
最后,设置表格中的ETA信息。先从改变可视区域的Cell开始:
for cell in self.tableView.visibleCells as! [DestinationTableViewCell] {
cell.userCoordinate = userCoordinate
}
为了防止表格滚动时更新Cell的内容,还需要改变tableView:cellForRowAtIndexPath
方法。
cell.userCoordinate = userCoordinate
一旦Cell上的坐标发生改变,需要触发一次更新进行显示。
var userCoordinate:CLLocationCoordinate2D? {
didSet {
etaLabel.text = ""
departureTimeLabel.text = "Departure Time:"
arrivalTimeLabel.text = "Arrival Time:"
distanceLabel.text = "Distance:"
guard let coordinate = userCoordinate else { return }
既然我们知道了用户坐标以及起始位置,就可以创建一个MKDirectionsRequest
对象来计算ETA信息。使用位置坐标初始化MKMapItem
对象,并设置起点和终点属性。将方向类型的属性设置为.Transit
。最后调用calculateETAWithCompletionHandler
来请求ETA信息。
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: coordinate, addressDictionary: nil))
request.destination = destination!.mapItem
request.transportType = .Transit
let directions = MKDirections(request: request)
directions.calculateETAWithCompletionHandler { response, error -> Void in
if let err = error {
self.etaLabel.text = err.userInfo["NSLocalizedFailureReason"] as? String
return
}
self.etaLabel.text = "\(response!.expectedTravelTime/60) minutes travel time"
self.departureTimeLabel.text = "Departure Time: \(response!.expectedDepartureDate)"
self.arrivalTimeLabel.text = "Arrival Time: \(response!.expectedArrivalDate)"
self.distanceLabel.text = "Distance: \(response!.distance) meters"
}
运行程序,结果如下:
点击地图后就会更新Cell中的ETA信息。最后一件事就是修改“View Route”按钮的动作处理方法,给它们添加一下代码:
guard let mapDestination = destination else { return }
let launchOptions = [MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeTransit]
mapDestination.mapItem.openInMapsWithLaunchOptions(launchOptions)
点击后就会打开地图应用,并显示从当前位置开始的路线图。
自定义大头针的颜色
上面应用的功能虽然完整,但是不能区分哪个是目标位置,哪个是用户。我们可以通过实现MKMapViewDelegate
来改变大头针的外观。
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let pin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
pin.pinTintColor = annotation === userAnnotation ? UIColor.redColor() : UIColor.blueColor()
return pin
}
pinTintColor
是iOS 9引入的新属性,它运行我们设置每个大头针顶部的颜色。如上所示,一旦传递给mapView:viewForAnnotation
的annotation
对象是用户的位置,我们就可以将它设置为红色,而其它设置为蓝色。这样就可以区分目标位置和用户所在的位置了。
更多越多
更多关于MapKit的信息可以观看WWDC session 206(What's New in MapKit)。别忘了我们可以在GitHub上下载到本文的示例代码。
戴维营教育
戴维营教育(Dive In Education),潜心做IT职业教育!紧跟时代潮流,不弄虚作假!不忘初心!