iOS 9: Day by Day 第十天 MapKit Transit

本文翻译自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"])

创建多个位置对象,并将它们存储在一个数组中,方便后面在地图上进行显示。

ViewControllerviewDidLoad方法里添加一下代码来将目标地址在地图上标记出来。

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:viewForAnnotationannotation对象是用户的位置,我们就可以将它设置为红色,而其它设置为蓝色。这样就可以区分目标位置和用户所在的位置了。

更多越多

更多关于MapKit的信息可以观看WWDC session 206(What's New in MapKit)。别忘了我们可以在GitHub上下载到本文的示例代码。

戴维营教育

戴维营教育(Dive In Education),潜心做IT职业教育!紧跟时代潮流,不弄虚作假!不忘初心!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容