From 6e3ec5afb279652383345459ba0e9617280a2dad Mon Sep 17 00:00:00 2001 From: Tres Spicher Date: Thu, 3 Dec 2015 12:50:44 -0800 Subject: [PATCH 1/3] allow Camelot to use iOS's significant-change location service --- THGLocation/LocationService.swift | 82 ++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/THGLocation/LocationService.swift b/THGLocation/LocationService.swift index 423c0ad..d7b32c4 100644 --- a/THGLocation/LocationService.swift +++ b/THGLocation/LocationService.swift @@ -46,11 +46,24 @@ More time and power is used going down this list as the system tries to provide so be conservative according to your needs. `Good` should work well for most cases. */ public enum LocationAccuracy: Int { + case Coarse = 0 case Good case Better case Best } +/** +Callback frequency setting. Lowest power consumption is achieved by combining LocationUpdateFrequency.ChangesOnly + with LocationAccuracy.Coarse + +- ChangesOnly: Notify listeners only when location changes. The granularity of this depends on the LocationAccuracy setting +- Continuous: Notify listeners at regular, frequent intervals (~1-2s) +*/ +public enum LocationUpdateFrequency: Int { + case ChangesOnly + case Continuous +} + public enum LocationAuthorization { /// Authorization for location services to be used only when the app is in use by the user. case WhenInUse @@ -95,17 +108,27 @@ public typealias LocationUpdateResponseHandler = (success: Bool, location: CLLoc public struct LocationUpdateRequest { let accuracy: LocationAccuracy + let updateFrequency: LocationUpdateFrequency let response: LocationUpdateResponseHandler + /** + Convenience initializer defaulting updateFrequency to .Continuous + */ + public init(accuracy: LocationAccuracy, response: LocationUpdateResponseHandler) { + self.init(accuracy: accuracy, updateFrequency: .Continuous, response: response) + } + /** Initializes a request to be used for registering for location updates. - parameter accuracy: The accuracy desired by the listener. Since there can be multiple listeners, the framework endeavors to provide the highest level of accuracy registered. + - parameter updateFrequency: The rate at which to notify the listener - parameter response: This closure is called when a update is received or if there's an error. */ - public init(accuracy: LocationAccuracy, response: LocationUpdateResponseHandler) { + public init(accuracy: LocationAccuracy, updateFrequency: LocationUpdateFrequency, response: LocationUpdateResponseHandler) { self.accuracy = accuracy self.response = response + self.updateFrequency = updateFrequency } } @@ -169,13 +192,33 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr } class LocationListener { + static let locationChangeThresholdMeters: [LocationAccuracy: CLLocationDistance] = [ + .Best: 0, + .Better: 10, + .Good: 100, + .Coarse: 200 + ] + weak var listener: AnyObject? var request: LocationUpdateRequest - + var previousCallbackLocation: CLLocation? + init(listener: AnyObject, request: LocationUpdateRequest) { self.listener = listener self.request = request } + + func shouldUpdateListenerForLocation(location: CLLocation) -> Bool { + switch request.updateFrequency { + case .Continuous: + return true + case .ChangesOnly: + if previousCallbackLocation == nil || previousCallbackLocation?.distanceFromLocation(location) >= LocationListener.locationChangeThresholdMeters[request.accuracy] { + return true + } + return false + } + } } // MARK: LocationUpdateProvider @@ -192,7 +235,8 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr }) calculateAndUpdateAccuracy() - manager.startUpdatingLocation() + startMonitoringLocation() + return nil } @@ -239,7 +283,25 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr } // MARK: Internal Interface + private func startMonitoringLocation() { + if self.shouldUseSignificantUpdateService() { + self.manager.startMonitoringSignificantLocationChanges() + } else { + self.manager.startUpdatingLocation() + } + } + /** + * There are two kinds of location monitoring in iOS: significant updates and standard location monitoring. + * Significant updates rely entirely on cell towers and therefore have low accuracy and low power consumption. + * They also fire infrequently. If the user has requested location accuracy higher than .Coarse or wants + * continuous updates, the significant location service is inappropriate to use. Otherwise, it is a better option. + */ + private func shouldUseSignificantUpdateService() -> Bool { + let hasContinuousListeners = allLocationListeners.filter({$0.request.updateFrequency == .Continuous}).count > 0 + return !(hasContinuousListeners || accuracy.rawValue > LocationAccuracy.Coarse.rawValue) + } + private func checkIfLocationServicesEnabled() -> NSError? { if CLLocationManager.locationServicesEnabled() { return nil @@ -266,6 +328,7 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr self.allLocationListeners.removeAtIndex(theIndex) if self.allLocationListeners.count == 0 { self.manager.stopUpdatingLocation() + self.manager.stopMonitoringSignificantLocationChanges() } }) @@ -290,6 +353,8 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr accuracy = computedAccuracy switch accuracy { + case .Coarse: + manager.desiredAccuracy = kCLLocationAccuracyKilometer case .Good: manager.desiredAccuracy = kCLLocationAccuracyHundredMeters case .Better: @@ -330,9 +395,12 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr synchronized(self, closure: { () -> Void in for locationListener in self.allLocationListeners { if locationListener.listener != nil { - dispatch_async(dispatch_get_main_queue(), { () -> Void in - locationListener.request.response(success: true, location: mostRecentLocation, error: nil) - }) + if locationListener.shouldUpdateListenerForLocation(mostRecentLocation) { + dispatch_async(dispatch_get_main_queue(), { () -> Void in + locationListener.previousCallbackLocation = mostRecentLocation + locationListener.request.response(success: true, location: mostRecentLocation, error: nil) + }) + } } else { locationListenersToRemove.append(locationListener) } @@ -361,7 +429,7 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr } if status == .AuthorizedWhenInUse || status == .AuthorizedAlways { - manager.startUpdatingLocation() + startMonitoringLocation() } } } From 7e2980120d1843e4ef42b1c0c338d9eb55d97a77 Mon Sep 17 00:00:00 2001 From: Tres Spicher Date: Thu, 3 Dec 2015 12:51:13 -0800 Subject: [PATCH 2/3] update README with updateFrequency parameter and replace println --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 07dd736..2d0b657 100644 --- a/README.md +++ b/README.md @@ -42,26 +42,34 @@ func startLocationUpdates() { let request = LocationUpdateRequest(accuracy: .Good) { (success, location, error) -> Void in if success { if let actualLocation = location { - println("LISTENER 1: success! location is (\(actualLocation.coordinate.latitude), \(actualLocation.coordinate.longitude))") + print("LISTENER 1: success! location is (\(actualLocation.coordinate.latitude), \(actualLocation.coordinate.longitude))") } } else { if let theError = error { - println("LISTENER 1: error is \(theError.localizedDescription)") + print("LISTENER 1: error is \(theError.localizedDescription)") } } } // Register the listener if let addListenerError = LocationUpdateService().registerListener(self, request: request) { - println("LISTENER 1: error in adding the listener. error is \(addListenerError.localizedDescription)") + print("LISTENER 1: error in adding the listener. error is \(addListenerError.localizedDescription)") } else { - println("LISTENER 1 ADDED") + print("LISTENER 1 ADDED") } } ``` Note that there can be an error in setting up the request and that is returned right away rather than in the handler block. You should check for that. +It is also possible to limit the callback frequency using the `updateFrequency` parameter of `LocationUpdateRequest` like so: + +```Swift +let request = LocationUpdateRequest(accuracy: .Good, updateFrequency: .ChangesOnly, ...) +``` + +Combining `accuracy: .Coarse` with `updateFrequency: .ChangesOnly` yields the lowest battery usage, at the expense of less accurate location data. + ## Contributions We appreciate your contributions to all of our projects and look forward to interacting with you via Pull Requests, the issue tracker, via Twitter, etc. We're happy to help you, and to have you help us. We'll strive to answer every PR and issue and be very transparent in what we do. From 9cea668f16683c02ce9a1622a84bc1340e4bbce9 Mon Sep 17 00:00:00 2001 From: Tres Spicher Date: Thu, 3 Dec 2015 16:06:56 -0800 Subject: [PATCH 3/3] .Always authorization is required for significant location updates --- README.md | 2 +- THGLocation/LocationService.swift | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2d0b657..c9de12a 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ It is also possible to limit the callback frequency using the `updateFrequency` let request = LocationUpdateRequest(accuracy: .Good, updateFrequency: .ChangesOnly, ...) ``` -Combining `accuracy: .Coarse` with `updateFrequency: .ChangesOnly` yields the lowest battery usage, at the expense of less accurate location data. +Combining `accuracy: .Coarse` with `updateFrequency: .ChangesOnly`, along with `requestAuthorization(.Always)` yields the lowest battery usage, at the expense of less accurate location data and infrequent updates. ## Contributions diff --git a/THGLocation/LocationService.swift b/THGLocation/LocationService.swift index d7b32c4..801e5b1 100644 --- a/THGLocation/LocationService.swift +++ b/THGLocation/LocationService.swift @@ -46,7 +46,7 @@ More time and power is used going down this list as the system tries to provide so be conservative according to your needs. `Good` should work well for most cases. */ public enum LocationAccuracy: Int { - case Coarse = 0 + case Coarse case Good case Better case Best @@ -284,10 +284,10 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr // MARK: Internal Interface private func startMonitoringLocation() { - if self.shouldUseSignificantUpdateService() { - self.manager.startMonitoringSignificantLocationChanges() + if shouldUseSignificantUpdateService() { + manager.startMonitoringSignificantLocationChanges() } else { - self.manager.startUpdatingLocation() + manager.startUpdatingLocation() } } @@ -295,11 +295,13 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr * There are two kinds of location monitoring in iOS: significant updates and standard location monitoring. * Significant updates rely entirely on cell towers and therefore have low accuracy and low power consumption. * They also fire infrequently. If the user has requested location accuracy higher than .Coarse or wants - * continuous updates, the significant location service is inappropriate to use. Otherwise, it is a better option. + * continuous updates, the significant location service is inappropriate to use. Finally, the user must have + * requested .Always authorization status */ private func shouldUseSignificantUpdateService() -> Bool { let hasContinuousListeners = allLocationListeners.filter({$0.request.updateFrequency == .Continuous}).count > 0 - return !(hasContinuousListeners || accuracy.rawValue > LocationAccuracy.Coarse.rawValue) + let isAccuracyCoarseEnough = accuracy.rawValue <= LocationAccuracy.Coarse.rawValue + return !hasContinuousListeners && isAccuracyCoarseEnough && authorization == .Always } private func checkIfLocationServicesEnabled() -> NSError? {