Core Location 电子围栏:入门
原文:Geofencing with Core Location: Getting Started
作者:Andy Pereira
譯者:kmyhy
更新說明:Andy Pereira 將本教程升級至 Xcode 9.3 和 Swift 4.1。
Geofencing 會在設備進入/離開指定的電子圍欄時通知應用程序。它可以讓你寫出一些很酷的應用程序,當你從家里出來時觸發通知,或者在附近出現最愛的商店時,用最近的、最多的訂單提示給用戶。在這個 Geofencing 教程中,你將學習如何在 iOS 和 swift 中使用區域檢測——即 Core Location 的 Region Monitoring API。
另外,你將創建一個基于定位提醒的 app,Geotify,它允許用戶基于物理位置創建提醒。讓我們開始吧!
開始
使用底部的 Dowload Materials 按鈕下載開始項目。它包含了一個用于在地圖上添加/刪除大頭釘簡單 UI。每個大頭釘表示一個指定位置的提醒項,我把它叫做 geotification。
Build & run,你會看到一張空空的地圖。
點擊 + 按鈕,添加一個 geotification。app 會另外顯示一張視圖,允許你設置 geotification 的各種屬性。
在本教程中,你將在蘋果的新總部卡布基諾添加一個大頭釘。如果不知道位置,打開谷歌地圖,用它找到正確的位置。要讓大頭釘定位精確,請放大地圖。
注:要在模擬器上使用 pinch 手勢,請按下 option 鍵,然后用 shift 鍵移動手指的中心點,然后放開 shift 鍵,拖動鼠標進行捏放。
Radius 表示從指定位置開始距離多少米,在這個位置上 iOS 將觸發通知。該通知可以是您希望在通知中顯示的任何消息。這個 app 還可以讓用戶通過頂部的 segment control 來指定在圓圈內是用進入還是退出來觸發通知。
在 Radius 上輸入 1000,在 Note 上輸入 Say Hi to Tim!,把第一個電子圍欄通知置于 Upon Entry 上。
填完后點擊 Add 按鈕。你會看到地圖上顯示了一個新的大頭釘,外面還圍了一個圓圈表示的電子圍欄:
點擊大頭釘,你會看到通知詳情,比如提醒內容和事件類型。不要點擊那個小叉叉,除非你想刪除它!
您可以隨意添加或刪除任意多的地理位置。由于該應用程序使用 UserDefaults 進行持久化,所以在重啟后的位置列表仍然會存在。
配置 Core Location 和權限
現在,你添加到地圖上的電子圍欄通知還沒有真正的功能,只能看看而已。要解決這個問題,你需要遍歷每個位置,使用 Core Location 監聽它的保護范圍。
在開始監控電子圍欄之前,你需要創建 CLLocationManager 實例并請求相應的權限。
打開 GeotificationsViewController.swift 聲明一個 CLLocationManager 常量。可以將它放在 var geotifications: [Geotification] = []: 一句之后。
let locationManager = CLLocationManager()然后修改 viewDidLoad() 代碼:
override func viewDidLoad() {super.viewDidLoad()// 1locationManager.delegate = self// 2locationManager.requestAlwaysAuthorization()// 3loadAllGeotifications() }代碼分為 3 步:
當應用程序提示用戶進行授權時,它將顯示 NSLocationAlwaysAndWhenInUseUsageDescription 字符串,它向用戶進行了很好的解釋,說明為什么該應用需要訪問用戶的位置。當你請求授權時,這個 key 是必需的。如果缺失這個 key,系統會忽略請求,并禁用定位服務。
Build & run,你會看到:
你已經完成了 app 的請求授權許可。好了,請點擊允許,確保 location manager 能夠在適當的時機收到委托回調。
在繼續下一步之前,還有一個小問題:用戶當前的位置沒有顯示在地圖圖上!默認情況下,map view 會禁用這個特性,同時,在導航欄左上角的 zoom 按鈕也不會起作用。
幸好這個問題很容易解決——你只需要在用戶同意授權之后開啟 current location屬性。
在 GeotificationsViewController.swift 的CLLocationManagerDelegate 擴展中添加委托方法:
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {mapView.showsUserLocation = (status == .authorizedAlways) }location manager 在授權狀態發生改變時調用 locationManager(_:didChangeAuthorizationStatus:) 方法,如果用戶同意授權,location manager 會在你初始化了 location mananger 并設置其 delegate 之后調用這個方法。
因此這個方法是檢查 app 是否被授權的理想場所。如果用戶已授權,你就可以啟用 mapview 的 showsUserLocation。
Build & run。如果你在真機上運行,你會看到 mapview 上顯示出大頭釘。如果在模擬器運行,請點擊 Debug ? Location ? Apple 菜單,就可以看見定位大頭釘了:
另外,現在導航欄上的 zoom 按鈕也可以使用了。
注冊電子圍欄
配置好 location manager 之后,你現在必須讓 app 注冊用戶要監控的電子圍欄范圍。
用戶的電子圍欄信息保存在 Geotification 模型中。但是,要對電子圍欄進行監控,Core Location 需要你將它們表示為 CLCircularRegion 對象。要滿足這個條件,你需要創建一個助手方法,將一個 Geonotification 映射為 CLCircularRegion。
打開 GeotificationsViewController.swift 添加方法:
func region(with geotification: Geotification) -> CLCircularRegion {// 1let region = CLCircularRegion(center: geotification.coordinate, radius: geotification.radius, identifier: geotification.identifier)// 2region.notifyOnEntry = (geotification.eventType == .onEntry)region.notifyOnExit = !region.notifyOnEntryreturn region }這個方法做了這些事情:
接著,當用戶添加了 geotification 之后,需要用一個方法來啟動對指定 geotification 的監控。
在 GeotificationsViewController 中加入方法:
func startMonitoring(geotification: Geotification) {// 1if !CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {showAlert(withTitle:"Error", message: "Geofencing is not supported on this device!")return}// 2if CLLocationManager.authorizationStatus() != .authorizedAlways {let message = """Your geotification is saved but will only be activated once you grant Geotify permission to access the device location."""showAlert(withTitle:"Warning", message: message)}// 3let fenceRegion = region(with: geotification)// 4locationManager.startMonitoring(for: fenceRegion) }上述代碼的執行步驟簡單說明如下:
寫完這個方法,你需要另一個方法,當用戶從 app 中刪除 geotification 時停止監控它。
在 GeotificationsViewController.swift,在 startMonitoring(geotificiation:) 方法下添加:
func stopMonitoring(geotification: Geotification) {for region in locationManager.monitoredRegions {guard let circularRegion = region as? CLCircularRegion, circularRegion.identifier == geotification.identifier else { continue }locationManager.stopMonitoring(for: circularRegion)} }這個方法簡單停止 locationManager 對指定 geotification 的監控。
開始和停止方法完成后,你可以用在添加、刪除 geotification 時調用它們了。首先是添加部分。
首先打開 GeotificationsViewController.swift,找到 addGeotificationViewController(_:didAddCoordinate:radius:identifier:note:eventType:) 方法。
addGeotificationViewController 在創建 geotification時調用這個委托方法。它負責創建一個新的 Geotification對 象并刷新地圖和 geotifications 數組。最后,它會調用 saveAllGeotifications(),這個個方法用新的 geotifications 數組作為參數,將它存到 UserDefaults 中。
現在,修改 addGeotificationViewController(_:didAddCoordinate:radius:identifier:note:eventType:) 方法為:
func addGeotificationViewController(_ controller: AddGeotificationViewController, didAddCoordinate coordinate: CLLocationCoordinate2D, radius: Double, identifier: String, note: String, eventType: Geotification.EventType ) {controller.dismiss(animated: true, completion: nil)// 1let clampedRadius = min(radius, locationManager.maximumRegionMonitoringDistance)let geotification = Geotification(coordinate: coordinate, radius: clampedRadius, identifier: identifier, note: note, eventType: eventType)add(geotification)// 2startMonitoring(geotification: geotification)saveAllGeotifications() }主要修改有兩處:
這樣,app 就能夠注冊電子圍欄的監控了。但是有一個限制:因為電子圍欄是系統共享資源,Core Location 限制每個 app 的最大監控電子圍欄數是 20。
出于本教程的目的,對這個限制的解決辦法(在最后面“接下來去哪兒”會有一些討論),是限制用戶能夠添加 geotification 的數目。
在 updateGeotificationCount() 最后添加代碼:
navigationItem.rightBarButtonItem?.isEnabled = (geotifications.count < 20)這行代碼在添加書達到上限時禁用 Add 按鈕。
最后,是刪除 geotification。這個功能是在mapView(_:annotationView:calloutAccessoryControlTapped:) 中進行的,當用戶點擊大頭釘上的 delete 按鈕會調用這個方法。
在 mapView(_:annotationView:calloutAccessoryControlTapped:)的 remove(geotification) 前面添加一句:
stopMonitoring(geotification: geotification)這會停止電子圍欄監控,然后刪除 geotification,保存修改到 UserDefaults。
這樣,你的 app 已經能夠監控和停止監控用戶的電子圍欄了。太好了!
Build & run。你發現沒有任何改變,但 app 已經能夠注冊監控區域了。但是,它還不能響應電子圍欄事件。別急——這是接下來的工作!
響應電子圍欄事件
需要實現幾個關于錯誤處理的委托方法。這會用在萬一有錯誤出現的時候。
在 GeotificationsViewController.swift, 在 CLLocationManagerDelegate 擴展中添加:
func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {print("Monitoring failed for region with identifier: \(region!.identifier)") }func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {print("Location Manager failed with the following error: \(error)") }這些委托方法簡單地輸出錯誤信息以便調試。
注:在你的生產項目中,你肯定想在錯誤處理時更多做些事情。例如,你不想安靜地輸出日志,而想通知用戶發生了什么。
然后,打開 AppDelegate.swift,你將在這里監聽并處理電子圍欄的進入/退出事件。
在頭部 import Core Location 框架:
import CoreLocation在 var window: UIWindow? 下添加新屬性:
let locationManager = CLLocationManager()將 application(_:didFinishLaunchingWithOptions:) 修改為:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil ) -> Bool {locationManager.delegate = selflocationManager.requestAlwaysAuthorization()return true }你讓 AppDelegate 接收電子圍欄事件。先不管 Xcode 在這里提示的錯誤警告,等會我們會解決它。你可能奇怪,“為什么要在 AppDelegate 而不是 view controller 中干這件事呢?”
iOS 會無時不刻地監控 app 注冊的電子圍欄, 哪怕 app 沒有運行了。如果設備在 app 不在運行的情況下觸發電子圍欄事件,iOS 會自動打開后臺中的 app。因此 AppDelegate 是處理這類事件的理想場所,因為 view controller 可能并沒有加載或者就緒。
你還可能奇怪,“為什么剛剛才創建的 CLLocationMananger 就知道要監控什么電子圍欄呢?”
你的 app 所注冊的一切電子圍欄都能被 app 中所有的 location mananger 訪問,因此無論你在哪里初始化 location mananger 都無所謂。很爽吧?:]
接下來就是實現電子圍欄事件的相關委托方法了。在此之前,需要創建一個處理電子圍欄事件的方法。
在 AppDelegate.swift 添加方法:
func handleEvent(for region: CLRegion!) {print("Geofence triggered!") }這個方法有一個 CLRegion 參數,它只是簡單地打印一個信息。別急——后面你會實現事件的處理。
然后,在 AppDelegate 的最后添加一個擴展:
extension AppDelegate: CLLocationManagerDelegate {func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {if region is CLCircularRegion {handleEvent(for: region)}}func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {if region is CLCircularRegion {handleEvent(for: region)}} }正如方法名所示,當設備進入某個區域時調用 locationManager(_:didEnterRegion:) 方法,當設備退出某個區域時調用 locationManager(_:didExitRegion:) 方法。
兩個方法都會收到一個 CLRegion 參數。你需要判斷它是不是 CLCircularRegion,因為如果 app 監控的是 iBeacon 時它有可能是一個 CLBeaconReion。如果 region 就是一個 CLCircularRegion,再調用 handleEvent(for:)。
注:iOS 在判斷到有穿過邊界的行為時觸發電子圍欄事件。如果用戶已經位于某個區域內部,iOS 不產生事件。如果你需要知道設備是否位于指定區域以內或者以外,你需要使用蘋果提供的 requestStateForRegion(_:) 方法。
現在 app 已經能夠監控電子圍欄事件了,你需要來測試一下。無論它能不能挑動你的神經,這都是一件值得興奮的事情,因為在本教程中,你終于能夠看到點結果了。
最真實的測試方式是在設備上運行你的 app,添加幾個 geotification 然后拿著手機到處走或者開車溜溜。但是,這并不明智,因為你無法在設備不插線的情況下,查看電子圍欄事件觸發的打印日志。此外,在你提交測試之前,確保這個 app 工作正常是一個好的做法。
幸好,有一個簡單的法子,不需要你離開你溫暖的小窩。Xcode 允許你在項目中使用 GPX 文件模擬測試位置。在開始項目中已經包含了一個!
打開 Supporting Files 組下的 SimulatedLocations.gpx,你會看到:
<?xml version="1.0"?> <gpx version="1.1" creator="Xcode"><wpt lat="37.3349285" lon="-122.011033"><name>Apple</name><time>2014-09-24T14:00:00Z</time></wpt><wpt lat="37.422" lon="-122.084058"><name>Google</name><time>2014-09-24T14:00:05Z</time></wpt> </gpx>這其實是一個 XML 文件,包含了兩個路徑點(wpt,waypoint):Google 的山景城和丘珀蒂諾的蘋果公園。你會看到每個路徑點有一個 time 節點。它們之間相差有 5 秒,因此用這個文件模擬位置時,它會花 5 秒鐘從蘋果走到谷歌。還有兩個 gpx 文件:Apple.gpx 和 Google.gpx。這是固定位置,你可以它們創建電子圍欄。
要模擬 GPX 文件中的位置,請 Build & run。當 app 進入主視圖控制器后,回到 Xcode,選擇調試工具欄的 Location 圖標,然后選擇 SimulatedLocations:
沿著兩個路徑點之間的路徑添加幾個 geotification。如果在注冊電子圍欄之前添加過 geotification,那么這些 geotification 不會有效,你可以刪除它們重新開始。
關于測試位置,一種好的做法是在每個路徑點上防止一個 geotification。這是可能的一種測試場景:
Google: Radius: 1000m, Message: "Say Bye to Google!", Notify on Exit Apple: Radius: 1000m, Message: "Say Hi to Apple!", Notify on Entry注:為了讓添加位置的時候更容易,你可以用額外提供的測試地點。
一旦添加了 geotification,你會在進入/離開電子圍欄時看到控制臺打印消息。如果你按下 Home 鍵或者鎖屏,讓 app 轉入后臺,每當你穿過電子圍欄時仍然能看到打印消息,盡管你無法虛擬地驗證這種行為。
注:模擬位置既可以在模擬器也可以在設備上運行。但是,模擬器不是很精確,無論進入還是退出電子圍欄,地觸發對應的事件的時機不是很一致。在設備上要好得多,或者更流暢,拿起手機來起來逛逛!
通知用戶電子圍欄事件
app 已經完成大半。這里,當設備穿過電子圍欄時通知用戶的工作就留給你了,因此請自行完成這個功能。
要通過委托回調獲取和 CLCircularRegion 相關的 note 描述字串,你必須從 UserDefaults 中檢索對應的 geotification。這也不足為道,因你您可以使用注冊時分配給 CLCircularRegion 的唯一 ID 來找到正確的 geotification。
在 AppDelegate.swift 添加一句導入語句:
import UserNotifications接著,添加一個工具方法:
func note(from identifier: String) -> String? {let geotifications = Geotification.allGeotifications()guard let matched = geotifications.filter {$0.identifier == identifier}.first else { return nil }return matched.note }這個工具方法根據 ID 從持久存儲中查找 geotification 的 note,然后返回。
現在你已經能返回電子圍欄的 note 了,可以編寫當電子圍欄事件觸發的代碼并將 note 用于提示的消息。
在 application(_:didFinishLaunchingWithOptions:) 方法 returns 之前添加:
let options: UNAuthorizationOptions = [.badge, .sound, .alert] UNUserNotificationCenter.current().requestAuthorization(options: options) { success, error inif let error = error {print("Error: \(error)")} }最后,添加這個方法:
func applicationDidBecomeActive(_ application: UIApplication) {application.applicationIconBadgeNumber = 0UNUserNotificationCenter.current().removeAllPendingNotificationRequests()UNUserNotificationCenter.current().removeAllDeliveredNotifications() }這段代碼提示用戶打開遠程通知。此外,還清空了已有的通知。
然后,修改 handleEvent(for:) 方法為:
func handleEvent(for region: CLRegion!) {// 如果 app 是 active 的,顯示一個 alertif UIApplication.shared.applicationState == .active {guard let message = note(from: region.identifier) else { return }window?.rootViewController?.showAlert(withTitle: nil, message: message)} else {// 否則顯示本地通知guard let body = note(from: region.identifier) else { return }let notificationContent = UNMutableNotificationContent()notificationContent.body = bodynotificationContent.sound = UNNotificationSound.default()notificationContent.badge = UIApplication.shared.applicationIconBadgeNumber + 1 as NSNumberlet trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)let request = UNNotificationRequest(identifier: "location_change",content: notificationContent,trigger: trigger)UNUserNotificationCenter.current().add(request) { error inif let error = error {print("Error: \(error)")}}} }如果 app 是激活(active)的,上面的代碼會用 alert controller 方式顯示 note。否則,顯示本地為通知。
Build & run,重復上一節所述的測試過程。當電子圍欄事件觸發,你會看到 alert 顯示出 note 上的備注:
按 Home 鍵或者鎖屏,將 app 切換至后臺。你仍然會繼續收到電子圍欄事件的通知。
這樣,你就擁有一個完整的、基于定位的提醒 app 了。好,請離開座位,把你的 app 帶出去 show 一下吧!
注:在測試 app 時,可能會發現通知發出的時間并不是剛好在你穿過邊界的時候。
這是因為 iOS 在判斷是否穿過邊界之前,有一個緩沖距離,和設備必須在新位置停留的最短時間。iOS 在內部定義了這些閾值,目的是減少用戶在極度接近電子圍欄邊界的情況下發出的錯誤通知。
此外,這些閾值好像受定位硬件性能的限制。從經驗上講,當在設備上啟用 Wi-Fi 時,電子圍欄會更加精確。
接下來去哪里?
恭喜你!你已經可以編寫你自己的電子圍欄 app 了!
你可以從頁尾的 Download Materials 按鈕處下載完成后的項目。
電子圍欄是一種很有用的技術,在市場營銷、資源管理、安全、家長控制、甚至游戲等領域都有許多實用而深遠的應用,你能達到什么樣的目標完全取決于你的想象力。更多信息可以閱讀蘋果的區域監控。
我希望你喜歡這篇教程。歡迎在下面留言或提問。
Download Materials
總結
以上是生活随笔為你收集整理的Core Location 电子围栏:入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解构淘宝SPM/SCM流量跟踪体系
- 下一篇: 剖析虚幻渲染体系(14)- 延展篇:现代