Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to choose callback frequency and add coarser location monitoring #2

Merged
merged 3 commits into from
Dec 4, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`, along with `requestAuthorization(.Always)` yields the lowest battery usage, at the expense of less accurate location data and infrequent updates.

## 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.
Expand Down
84 changes: 77 additions & 7 deletions THGLocation/LocationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
Expand All @@ -192,7 +235,8 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr
})

calculateAndUpdateAccuracy()
manager.startUpdatingLocation()
startMonitoringLocation()

return nil
}

Expand Down Expand Up @@ -239,7 +283,27 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr
}

// MARK: Internal Interface
private func startMonitoringLocation() {
if shouldUseSignificantUpdateService() {
manager.startMonitoringSignificantLocationChanges()
} else {
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. Finally, the user must have
* requested .Always authorization status
*/
private func shouldUseSignificantUpdateService() -> Bool {
let hasContinuousListeners = allLocationListeners.filter({$0.request.updateFrequency == .Continuous}).count > 0
let isAccuracyCoarseEnough = accuracy.rawValue <= LocationAccuracy.Coarse.rawValue
return !hasContinuousListeners && isAccuracyCoarseEnough && authorization == .Always
}

private func checkIfLocationServicesEnabled() -> NSError? {
if CLLocationManager.locationServicesEnabled() {
return nil
Expand All @@ -266,6 +330,7 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr
self.allLocationListeners.removeAtIndex(theIndex)
if self.allLocationListeners.count == 0 {
self.manager.stopUpdatingLocation()
self.manager.stopMonitoringSignificantLocationChanges()
}
})

Expand All @@ -290,6 +355,8 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr
accuracy = computedAccuracy

switch accuracy {
case .Coarse:
manager.desiredAccuracy = kCLLocationAccuracyKilometer
case .Good:
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
case .Better:
Expand Down Expand Up @@ -330,9 +397,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)
}
Expand Down Expand Up @@ -361,7 +431,7 @@ class LocationManager: NSObject, LocationUpdateProvider, LocationAuthorizationPr
}

if status == .AuthorizedWhenInUse || status == .AuthorizedAlways {
manager.startUpdatingLocation()
startMonitoringLocation()
}
}
}
Expand Down