神刀安全网

iOS 9 by Tutorials 笔记(十四)

Chapter 14 Location and Mapping

尽管 iOS 的地图服务被大家广为诟病,但 Apple 每年都会持续改进,iOS 9 也不例外,MapKit 和 Core Location 迎来一大波更新。

其中最有用的一个改进就是在地图上增加了行程导航,本章将学习这些新特性:

  • 自定义地图外观的新方法
  • 行程导航
  • 估计行程的时间
  • 使用 Core Location 进行单个位置更新

这一章,Café Transit 的示例应用程序是为所有的咖啡爱好者开发的。它可以帮助你寻找那些称赞的咖啡。目前,它只显示附近的的一小撮咖啡馆。而当你完成了这一章,App 会显示大量的有用的信息,包括每个咖啡店,评级,定价信息和开放时间。还会提供到指定咖啡店的行程导航信息,以及告诉你什么时候出发和什么时候到达。

Getting started

熟悉下 Demo 程序

iOS 9 by Tutorials 笔记(十四)

  • ViewController.swift
    • setupMap() 设置地图并限定显示区域
    • addMapData() 从 model 中载入地图注释信息
  • CoffeeShop.swift 咖啡馆的 model 信息,并负责从 plist 载入信息
  • CoffeeShopPinDetailView.swiftCoffeeShopPinDetailView.xib 负责表示自定义的注释,该注释将会显示评分、价格信息和营业时间

Customizing maps

iOS 9 之前你只能通过编程的方式 开启/关闭 地图上的特定建筑物。iOS 9 介绍了三个新的 Boolen 属性,让你 开启/关闭 地图上的 compass -罗盘, scale bar -比例尺, traffic -交通流量

iOS 9 by Tutorials 笔记(十四)

我们可以为 Café Transit 开启比例尺,在 setupMap() 里开启

mapView.showsScale = true   

iOS 9 by Tutorials 笔记(十四)

随着对地图的缩放,比例尺也会跟着变化

Customizing map pins

在 iOS 9 中,苹果用 pinTintColor 替代了 pincolor ,新的属性允许你将标记大头针设置成自己喜欢的颜色了(原属性只能设红、绿、紫三种颜色)

我们来将五星评价的餐厅在地图上设为黄色,其余的设为棕色

if annotation.coffeeshop.rating.value == 5 {     annotationView!.pinTintColor =     UIColor(red:1, green:0.79, blue:0, alpha:1) } else {   annotationView!.pinTintColor =     UIColor(red:0.419, green:0.266, blue:0.215, alpha:1) } 

iOS 9 by Tutorials 笔记(十四)

Customizing annotation callouts

咖啡馆在地图上以大头针形式标注,且当你点击大头针 annotation view 时,会显示一个标注信息 callout ,展示关于该咖啡馆的额外的信息

iOS 9 by Tutorials 笔记(十四)

在 iOS 9 之前,你想要在 annotation view 里添加一个自定义 View 并不是一件容易的事情。但现在 iOS 9 让事情变得简单了。 MKAnnotationView 现在有一个新属性 detailCalloutAccessoryView 来展示这个 callouts ,并且该 View 并没什么限制。

Managing callout size

Callouts 将根据你的自定义视图的大小来调整自己的尺寸。你的自定义 callouts 可以利用下面两种方式:

  • 在你的自定义视图中使用 Auto Layout 来布局
  • 你可以覆盖 intrinsicContentSize 来定制你需要的尺寸

这里用了 CoffeeShopPinDetailView.XIB 来设计自定义的 Callout ,在 XIB 中我们使用 UIStackViewAuto Layout 来布局

自定义的 callouts 并不会铺满整个标注信息视图,他会显示一个标题和四周留白:

iOS 9 by Tutorials 笔记(十四)

没办法修改标题和四周的留白区域

Adding a custom callout accessory view

理论学习完了,现在我们来添加自定义的 callout,UI 已经在 CoffeeShopPinDetailView.xib 中设计好了

iOS 9 by Tutorials 笔记(十四)

打开 ViewController.swiftmapView(_:viewForAnnotation:) 里加入下面的方法:

let detailView = UIView.loadFromNibNamed(identifier) as!     CoffeeShopPinDetailView detailView.coffeeShop = annotation.coffeeshop   annotationView!.detailCalloutAccessoryView = detailView   

首先从 XIB 文件中载入 CoffeeShopPinDetailView ,然后为其分配当前的 coffeeshop ,最后设置 view 的 detailCalloutAccessoryView 属性即可。

iOS 9 by Tutorials 笔记(十四)

点击 Yelp 按钮会打开 Safari 将你带到该咖啡店在 Yelp 的评价主页,但时钟按钮现在还点不了,稍后我们来实现

Supporting time zones

我们上面添加的 callouts 包含一个标识,指示当前咖啡馆是否正在营业:

iOS 9 by Tutorials 笔记(十四)

static var timeZone = NSTimeZone(abbreviation: "PST")!  /// Calculates whether a coffee shop is currently open for business var isOpenNow: Bool {       let calendar = NSCalendar.currentCalendar()     let nowComponents = calendar.componentsInTimeZone(CoffeeShop.timeZone, fromDate: NSDate())     ... 

isOpenNow 是个计算属性,用来标识当前营业状态。这里使用了 NSDate() 得到当前时间,并转换成咖啡馆所在时区的时间,以此来判断咖啡馆现在是否开始营业了。

很简单不是吗?但我们观察这一句:

static var timeZone = NSTimeZone(abbreviation: "PST")!   

这里硬编码了时区 PST,虽然我们的 APP 只包含了三藩的咖啡馆,但如果能根据地理位置自动推断出对应的时区时间岂不是更赞!

iOS 9 为 MKMapItemCLPlacemark 添加了 timeZone 属性,我们利用该属性来得到符合当前地理位置的正确时间。

static func allCoffeeShops() -> [CoffeeShop] {       guard let path = NSBundle.mainBundle().pathForResource("sanfrancisco_coffeeshops", ofType: "plist"),       let array = NSArray(contentsOfFile: path) as? [[String : AnyObject]] else {         return [CoffeeShop]()     }      // 1     let shops = array.flatMap { CoffeeShop(dictionary: $0) }       .sort { $0.name < $1.name }     // 2     let first = shops.first!     let location = CLLocation(latitude: first.location.latitude,       longitude: first.location.longitude)     // 3     let geocoder = CLGeocoder()     geocoder.reverseGeocodeLocation(location) { (placemarks, _) in       if let placemark = placemarks?.first, timeZone =       placemark.timeZone {       self.timeZone = timeZone       }     }     return shops   } 
  1. 根据 plist 文件得到所有的咖啡馆
  2. 找出第一个咖啡馆的地理位置:经纬度
  3. 将经纬度转码成地理信息,在回调闭包得到 timeZone ,并设置为咖啡馆( CoffeeShop )的 timeZone

现在运行,你会发现每个咖啡馆会基于旧金山时间来显示是否营业,而不是你当地的时间。

在实际项目中,你需要判断每个咖啡馆的地理位置,因为他们可能分布在不同时区

Simulating your location

我们当前所有的咖啡馆都在旧金山,所以我们也要假装自己在旧金山。比较幸运的是 Xcode 很容易就能做到这一点

点击 CafeTransit scheme 选择 Edit Scheme

iOS 9 by Tutorials 笔记(十四)

勾选 Allow Location Simulation

iOS 9 by Tutorials 笔记(十四)

Making a single location request

在 iOS 9 之前,得到用户当前位置需要一个相当繁琐的过程,你要创建一个 CLLocationManager ,实现一些代理方法,然后调用 startUpdatingLocation() ,然后随着用户位置移动,会反复调用 location manager delegate 方法。如果一旦达到了期望的精度,你需要调用 stopUpdatingLocation() 来让 location manager 停止工作,不然你的手机电量会很快耗光。

在 iOS 9 将这些繁琐的过程封装成了一个方法: requestLocation() ,他仍然利用了 API 中的 delegate 回调方法,但不需要你手动控制开始结束了。你进需要设置期望的精度,然后 Core Location 会提供给你位置信息。他只调用一次 delegate 并且只返回一个位置。

理论听够了,来看实际例子

Adding a location manager

ViewController.swift 的类声明下添加两个对象:

lazy var locationManager = CLLocationManager()   // 用来存储用户位置 var currentUserLocation: CLLocationCoordinate2D?   

viewDidLoad() 中设置代理和期望精度:

locationManager.delegate = self   locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters   

下面我们来实现这个 CLLocationManagerDelegate 代理

// MARK:- CLLocationManagerDelegate extension ViewController: CLLocationManagerDelegate {     // 查看该 App 是否有权限查看用户位置信息,如果有,请求用户位置   func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {       if (status == CLAuthorizationStatus.AuthorizedAlways ||         status == CLAuthorizationStatus.AuthorizedWhenInUse) {       locationManager.requestLocation()     }   }   // 存储返回的第一个位置坐标   func locationManager(manager: CLLocationManager,     didUpdateLocations locations: [CLLocation]) {     currentUserLocation = locations.first?.coordinate   }   // 记录错误   func locationManager(manager: CLLocationManager,     didFailWithError error: NSError) {     print("Error finding location: +       /(error.localizedDescription)")   }  } 

现在你需要从其他地方调用 requestLocation()

下面在 ViewController.swift 添加一个私有方法用来获取用户位置:

private func requestUserLocation() {     // 在地图上显示用户位置   mapView.showsUserLocation = true   // 请求之前判断权限,有权限更新位置,无权限先鉴权   if CLLocationManager.authorizationStatus() == .AuthorizedWhenInUse {     locationManager.requestLocation()   } else {     locationManager.requestWhenInUseAuthorization()   } } 

注意,当你调用 requestWhenInUseAuthorization() 请求权限时,必须已经提前在 Info.plist 文件中,为 key: NSLocationWhenInUseUsageDescription 设置好了对应的键值。这个键值通常是一个字符串,会随鉴权请求弹窗一起展示给用户。

iOS 9 by Tutorials 笔记(十四)

我们让地图一出现在屏幕上就请求用户位置

if annotation.coffeeshop.rating.value == 5 {     annotationView!.pinTintColor =     UIColor(red:1, green:0.79, blue:0, alpha:1) } else {   annotationView!.pinTintColor =     UIColor(red:0.419, green:0.266, blue:0.215, alpha:1) } 

0

最后我们在 MKMapViewDelegate 中将用户位置传递给选中的 annotation (咖啡馆大头针标记),为下一节交通导航路线做准备

if annotation.coffeeshop.rating.value == 5 {     annotationView!.pinTintColor =     UIColor(red:1, green:0.79, blue:0, alpha:1) } else {   annotationView!.pinTintColor =     UIColor(red:0.419, green:0.266, blue:0.215, alpha:1) } 

1

Requesting transit directions

既然已经得到用户位置,现在让我们来添加前往指定咖啡馆的交通搭乘路线,这次在 CoffeeShopPinDetailView.swift 中添加一个 helper 方法

if annotation.coffeeshop.rating.value == 5 {     annotationView!.pinTintColor =     UIColor(red:1, green:0.79, blue:0, alpha:1) } else {   annotationView!.pinTintColor =     UIColor(red:0.419, green:0.266, blue:0.215, alpha:1) } 

2

  1. 创建一个 MKPlacemark 用来存储你的坐标, Placemarks 通常有一个相对应的地址,而 coffee shop model 提供了基本的店名(从通讯录中)
  2. 用第一步的 placemark 初始化一个 MKMapItem (封装地图上某一点的相关信息)
  3. 指定导航模式,这里一共有三种可供设置:
    • MKLaunchOptionsDirectionsModeDriving 开车
    • MKLaunchOptionsDirectionsModeWalking 步行
    • MKLaunchOptionsDirectionsModeTransit 搭乘公共交通
  4. 以指定的导航模式打开地图,并显示导航线路

我们在 transitTapped() 中调用这个 helper 方法

if annotation.coffeeshop.rating.value == 5 {     annotationView!.pinTintColor =     UIColor(red:1, green:0.79, blue:0, alpha:1) } else {   annotationView!.pinTintColor =     UIColor(red:0.419, green:0.266, blue:0.215, alpha:1) } 

3

运行,在地图上点击标记的咖啡馆大头针,弹出的 callout 视图中点按 train 图标,你就会直接进入公共交通导航界面

iOS 9 by Tutorials 笔记(十四)

Querying transit times

最后一个新特性是,MapKit 允许你查询公共交通行程信息。 MKETAResponse 类在 iOS 9 新增了下面一些有用的属性:

if annotation.coffeeshop.rating.value == 5 {     annotationView!.pinTintColor =     UIColor(red:1, green:0.79, blue:0, alpha:1) } else {   annotationView!.pinTintColor =     UIColor(red:0.419, green:0.266, blue:0.215, alpha:1) } 

4

这些属性告诉你旅行的距离,时间以及出发时间和到达时间。

同样在地图上点击标记的咖啡馆大头针,弹出的 callout 视图中点按时钟图标,整个 view 会以动画的形式向上展示预估的出发时间和达到时间,下面让我们来实现这种效果:

还是在 CoffeeShopPinDetailView.swift 中,刚才的 helper 方法下面添加:

if annotation.coffeeshop.rating.value == 5 {     annotationView!.pinTintColor =     UIColor(red:1, green:0.79, blue:0, alpha:1) } else {   annotationView!.pinTintColor =     UIColor(red:0.419, green:0.266, blue:0.215, alpha:1) } 

5

  1. 一旦确认用户位置,初始化一个 MKDirectionsRequest 实例
  2. 创建两个 MKMapItem 实例,一个表示用户位置,另一个表示咖啡馆的位置。这里没有用到 addressDictionary 是因为我们只需要经纬度信息。
  3. 设置 MKDirectionsRequest 对象的源地址和目的地址,交通工具类型
  4. 用上面的 request 创建一个 MKDirections 对象实例,并执行 ETA 计算
  5. 最终结果将以闭包形式返回,如果收到一个成功的响应,则根据 response 更新在 view 上更新出发时间和到达时间

最后实现点按时钟图标获取行程时间的方法,依然是在 CoffeeShopPinDetailView.swift

if annotation.coffeeshop.rating.value == 5 {     annotationView!.pinTintColor =     UIColor(red:1, green:0.79, blue:0, alpha:1) } else {   annotationView!.pinTintColor =     UIColor(red:0.419, green:0.266, blue:0.215, alpha:1) } 

6

现在,当你按下时钟(clock)图标,时间视图会向上滑出,并向苹果服务器发送计算请求,最终行程的计算结果会自动更新子时间视图上。

iOS 9 by Tutorials 笔记(十四)

-EOF-

原文  http://chengway.in/ios-9-by-tutorials-bi-ji-shi-si/

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » iOS 9 by Tutorials 笔记(十四)

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮