Core Location in iOS 8

自從 iPhone 存在以來,位置服務就一直處於很是重要的位置。Maps.app 是第一代 iPhone 裏殺手鐗的功能之一。Core Location API 也在 iPhone OS SDK 最初的公開版本里就存在了。每一次發佈 iOS,Apple 都會給這個庫逐步添加新功能,好比後臺運行的位置服務,座標化,以及室內定位( iBeacons )。數組

iOS 8 仍然繼續堅決的延續着這個進程。跟其餘最新的更新相似,Core Location 被改動了很多,不論是容許開發者作以前並不被容許的開發,仍是幫助維護用戶隱私。更特別的是,iOS 8 給 Core Location 帶來了三個主要的改進:更分化的權限,室內定位以及訪問監控。session

權限

一個 app 總有各類各樣的理由須要獲得你的位置信息。一個可以提示你每一個轉彎在哪裏的 GPS 應用就須要持續得到你的位置信息,才能夠在轉彎的時候提示你。一個餐廳推薦的 app 也須要獲得你的位置信息(即使它並無打開的狀況下),才能夠在你到你朋友點讚的餐廳附近的時候能收到推送消息。一個 Twitter 應用在發推的時候也可能須要你的位置,但在你不使用的時候不該該監控你的位置。app

在 iOS 8 以前,位置服務的權限是二元的:你要麼賦予一個應用獲得使用位置服務的權限,要麼不給。你能夠在 Settings.app 查看哪些 app 能夠在後臺取得你的位置信息,但除了徹底不讓這個 app 使用位置服務以外,你不能作任何的事來阻止它獲取位置信息。異步

iOS 8 修改了這個問題,它把位置服務權限拆分紅了 2 個不一樣的受權。工具

  • 「使用期間」 的受權會只容許應用在 - 就跟你猜想的同樣 - 使用期間取得你的位置信息。this

  • 「始終」 的受權則跟以前版本的 iOS 那樣,會給應用後臺權限。url

這是對用戶隱私的一個重大改進,但對於咱們開發者來講則意味着多一些的工做。spa

取得權限

在早前的 iOS 版本中,獲取位置服務權限是隱式的。好比 CLLocationManager,若是應用程序尚未被許可或者以前被拒絕了的話,下面的代碼會觸發系統彈出提示框向用戶獲取位置服務的受權:操作系統

Swift代理

import Foundation import CoreLocation let manager = CLLocationManager() if CLLocationManager.locationServicesEnabled() {     manager.startUpdatingLocation() }

把事情簡化一下,假定咱們聲明瞭一個 manager 實例做爲全部例子的成員變量,它的 delegate 是它的 owner。

讓 CLLocationManager 取得最新的位置的這個操做會讓系統彈出是否容許位置服務的提示。

在 iOS 8,取得權限和使用位置服務已經分紅兩個動做了。分別用兩個不一樣的方法取得權限:requestWhenInUseAuthorization 和 requestAlwaysAuthorization。前者只能讓應用在使用的時候有權獲取位置數據;後者會獲得跟以前 iOS 同樣的後臺位置服務。

Swift

if CLLocationManager.authorizationStatus() == .NotDetermined {     manager.requestAlwaysAuthorization() }

或者

Swift

if CLLocationManager.authorizationStatus() == .NotDetermined {     manager.requestWhenInUseAuthorization() }

由於這是異步的,應用不能當即開始使用位置服務。取而代之的是,應用必須實現 locationManager:didChangeAuthorizationStatus 的 delegate 方法,這個方法會在用戶改變權限狀態的時候調用。

若是用戶以前已經受權了位置服務,那麼在每次位置管理器被初始化,而且 delegate 被設置了相應的權限狀態的狀況下這個代理方法仍然會被調用。這使得一個單一的代碼路徑使用定位服務更爲方便。

Swift

func locationManager(manager: CLLocationManager!,                      didChangeAuthorizationStatus status: CLAuthorizationStatus) {     if status == .AuthorizedAlways || status == .AuthorizedWhenInUse {         manager.startUpdatingLocation()         // ...     } }

描述字符串

想在 iOS 8 中使用定位,另外一個改變是必須的。在這以前,應用能夠選擇性的在 Info.plist 中包含 'NSLocationUsageDescription' 的關鍵字。這個值是一個純文本的字符串,向用戶說明了應用預期要使用位置服務。如今這個值被拆分紅了兩個不一樣的關鍵字(NSLocationWhenInUseUsageDescription 和 NSLocationAlwaysUsageDescription),並且是必填的;若是你不添加對應的關鍵字就去調用 requestWhenInUseAuthorization 或 requestAlwaysAuthorization,那麼將不會有任何的彈出提示給用戶。

Core Location Always Authorization

Core Location When In Use Authorization

獲取多個權限

另外一個值得注意的細節是受權的彈出框會只顯示一次。在 CLLocationManager.authorizationStatus() 返回除 NotDetermined 以外的值以後,無論調用 requestWhenInUseAuthorization() 或 requestAlwaysAuthorization() 都不會有一個 UIAlertController 顯示出來了。在用戶最初的選擇以後,惟一改變受權的方式是到 Settings.app 或者到隱私設置,又或者是應用本身的設置頁面。

舊的受權機制各類不方便的狀況下,如今讓應用在它的生存週期內詢問不管是「使用期間」仍是「始終」的權限的機制明顯複雜多了,更不方便了。爲了緩解這一點,Apple 引入了一個字符串常量,UIApplicationOpenSettingsURLString,它存儲了一個 URL 用來打開當前應用在 Settings.app 對應的頁面。

下面的例子顯示瞭如何在應用裏彈出兩種類型的權限獲取窗口,若是你的應用打算獲取始終的權限的話,能夠參考一下。

Swift

switch CLLocationManager.authorizationStatus() {     case .Authorized:         // ...     case .NotDetermined:         manager.requestWhenAlwaysAuthorization()     case .AuthorizedWhenInUse, .Restricted, .Denied:         let alertController = UIAlertController(             title: "Background Location Access Disabled",             message: "In order to be notified about adorable kittens near you, please open this app's settings and set location access to 'Always'.",             preferredStyle: .Alert)         let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)         alertController.addAction(cancelAction)         let openAction = UIAlertAction(title: "Open Settings", style: .Default) { (action) in             if let url = NSURL(string:UIApplicationOpenSettingsURLString) {                 UIApplication.sharedApplication().openURL(url)             }         }         alertController.addAction(openAction)         self.presentViewController(alertController, animated: true, completion: nil) }

Core Location Settings Alert

Core Location Settings Location Never

Core Location Settings Location Always

向後兼容

全部這些新的 API 都只支持 iOS 8。對於要支持 iOS 7 或以前 iOS 版本的應用,則必須維護兩部分代碼,一個是爲 iOS 8 獲取權限的,同時還須要維護以前的獲取位置更新的方法。一個簡單的實現會看上去像下面這樣:

Swift

func triggerLocationServices() {     if CLLocationManager.locationServicesEnabled() {         if self.manager.respondsToSelector("requestWhenInUseAuthorization") {             manager.requestWhenInUseAuthorization()         } else {             startUpdatingLocation()         }     } } func startUpdatingLocation() {     manager.startUpdatingLocation() } // MARK: - CLLocationManagerDelegate func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {     if status == .AuthorizedWhenInUse || status == .Authorized {         startUpdatingLocation()     } }

創建用戶的信任

如今在 iOS 8 上有一個共同的變化趨勢是:它們更容易獲得用戶的信任。

顯式的請求的受權,鼓勵應用程序在用戶試圖作一些事情以前不請求受權。這包括了用使用說明來很清晰的解釋爲何你須要的訪問位置信息,以及應用程序將如何使用它。「使用期間」 和 「始終」 的區別受權,使用戶感到輕鬆,由於應用只獲得了須要的數據。

固然,這些新的 API 並不能阻止應用作跟以前同樣的事。全部應用 「須要」 針對支持 iOS 8 的事只是添加對 useAlwaysAuthorization 的調用,以及添加一個全局適用的字符串。但隨着這些新的變化, Apple 正在傳達一個重要的信息,那就是你應該尊重你的用戶。一旦用戶習慣了像這樣尊重用戶隱私的應用程序,不難想象的是,不負責任地使用定位服務 的應用會在 App Store 獲得更多負面的評分。

室內定位追蹤

若是一我的仔細閱讀了 CoreLocation.framework 的 API 改動文件,會發現最使人費解的改動之一是引進了 CLFloor,一個新的只有至關簡單的接口對象:

SwiftObjective-C

class CLFloor : NSObject {     var level: Int { get } }

就是這樣。僅一個屬性,一個整形值來表示當前位置處於建築物的第幾層樓。

歐洲人確定會很高興的發現一樓是用 '1' 表示的, 而不是 '0'。

由 CLLocationManager 返回的 CLLocation 對象可能包括一個 floor 屬性,但若是你是寫一個使用定位服務的示例應用程序,你會發現 CLLocation 對象的 floor 屬性老是 nil

這是由於該API的變化只是 iOS8 中引入的室內定位跟蹤這個大功能的冰山一角。對於大型空間的應用開發,例如藝術博物館或百貨公司這種, Apple 如今已經有結合了無線,GPS,蜂窩,和 室內定位數據的內置 Core Location API 支持 IPS 。

也就是說,這個新功能的信息使人吃驚的來之不易。該項目目前被很好的嚴格限制訪問了,僅容許已經過從 Apple Maps Connect 申請的程序。關於該項目的的有限信息是在今年的WWDC(Session 708: Taking Core Location Indoors)被初露頭角的,但大部分的後臺細節都被隱藏在被關上的門以後了。對於大多數的咱們來講,別無選擇的,只能打消沒有用的好奇心。

CLVisit

不少應用程序,使用位置監控的緣由是肯定用戶是否在某個肯定的地方。概念上講,你在想的是諸如「地方」或「訪問」的名詞術語,而不是原始的 GPS 座標。

然而,除非你能夠得益於使用區域監視(被限制在一個相對小的數量的區域)或室內定位( iBeacon )測距(這要求把室內定位硬件真正安裝在一個空間內),不然用 Core Location 的後臺監控工具並非很是適合。開發一個登記應用或像 Moves 這樣的全面的日誌應用,位置監控和花費不少時間作特定處理通常意味着消耗大量的電量。

在 iOS 8 裏, Apple 曾試圖經過引進 CLVisit 來解決這個問題, 這是一種新型的後臺位置的監控。一個 CLVisit 表示該用戶已經處於某個位置的時間長度,包括一個座標和開始/結束的時間戳。

理論上講,使用訪問監控並不比任何其餘後臺定位跟蹤作更多的事。簡單地調用 manager.startMonitoringVisits() 將啓用後臺訪問跟蹤,假設用戶贊成受權你的應用程序「始終」的使用權限。一旦啓動,你的應用程序將在有位置更新的時候在後臺被喚醒,不像基本的定位監控,若是系統有個訪問更新的隊列(一般可使更新延遲),你的 delegate 方法將被調用屢次,每一個單一的訪問調用一次,而不是一個包含 CLLocation 對象的數組調用 locationManager:didReceiveUpdates:。調用 manager.stopMonitoringVisits() 會中止跟蹤。

處理訪問

每一個 CLVisit 對象包含了一些基本屬性:平均座標,水平精度和到達日期和離開時間。

每次一個訪問被追蹤到,CLLocationManagerDelegate 可能會被告知兩次:一次是在用戶剛抵達一個新的地方的時候,以及當用戶離開的時候。你能夠經過檢查 departureDate 屬性來分辨它們; NSDate.distantFuture() 的離開時間意味着用戶還在那兒。

Swift

func locationManager(manager: CLLocationManager!, didVisit visit: CLVisit!) {     if visit.departureDate.isEqualToDate(NSDate.distantFuture()) {         // User has arrived, but not left, the location     } else {         // The visit is complete     } }

實現須知

至少到 iOS 8.1,CLVisit 還不是那麼精確。開始和結束時間通常有一兩分鐘的偏差,是否訪問某個地方的線路邊際有點模糊。在咖啡店的角落躲一分鐘可能不會觸發訪問,但在等一個特別長的紅綠燈的時候卻有可能觸發。極可能 Apple 將在操做系統的後續升級的時候提高訪問檢測的質量,但如今若是你的應用程序對訪問檢測的精度要求很高的話,你最好不要依賴 CLVisit

相關文章
相關標籤/搜索