MMPReactiveCoreLocation is a reactive library for using CoreLocation and iBeacon with ReactiveCocoa.
Important Notes:
- Version 0.6 has been redesigned and rewritten from scratch and is not compatible with previous versions. Documentation for version 0.5.* is still available here, 0.4.* is here.
Features:
- Globally accessible signals with intelligent resource management for automatically starting and stopping underlying location manager's services.
- No more delegates, all CLLocationManager's functionalities are available as signals.
- Signals for location-related updates, including one-time location query.
- Signals for region monitoring updates, including iBeacon monitoring and ranging.
- Signals for iOS 8 visit monitoring.
- Signals for location manager status updates and errors.
- Supports iOS 8 "Always" and "WhenInUse" authorization.
Although most of the CoreLocation services are implemented, some are not tested and should be considered as alpha quality. Features documented here are tested and should work:
- Location subscription
- Single location subscription
- Significant changes subscription
- Region monitoring events subscription
- Stopping subscription
- Custom location manager settings
- Handling errors and status changes
- Manual authorization request
MMPReactiveCoreLocation is available through CocoaPods, to install it simply add the following line to your Podfile:
pod "MMPReactiveCoreLocation"
The easiest way to subscribe to a location stream with sensible default settings is by calling locations
method to get the signal:
// import the header
#import <MMPReactiveCoreLocation/MMPReactiveCoreLocation.h>
// build service, subscribe to 'locations' signal
[[[MMPReactiveCoreLocation service]
locations]
subscribeNext:^(CLLocation *location) {
NSLog(@"[INFO] received location: %@", location);
}];
Calling this exact same code from multiple places in the application will not produce multiple CLLocationManager
+CLLocationManagerDelegate
sets. The library will manage, start and stop shared instances of CLLocationManager
+CLLocationManagerDelegate
based on settings specified when defining the service. For example, following code will create a new CLLocationManager
+CLLocationManagerDelegate
set because it requires a custom activityType
:
[[[[MMPReactiveCoreLocation service]
activityType:CLActivityTypeFitness]
locations]
subscribeNext:^(CLLocation *location) {
NSLog(@"[INFO] received location: %@", location);
}];
If you don't need a constant stream of location updates, you can use location
(note the lack of plural s
) to get the latest location once and the library will automatically stop CLLocationManager and cleanup resources:
// one-time location
[[[MMPReactiveCoreLocation service]
location]
subscribeNext:^(CLLocation *location) {
NSLog(@"[INFO] received single location: %@", location);
}];
There is a useful option specific to single location subscription called timeout
. This option can be used to specify whether the signal should give up after after an amount of time waiting for location. This is particularly useful when the application should keep functioning even when there currently is no location available (underground, etc.). Timing out the service will produce signal error as shown in the following example:
// one-time location with 5 sec. timeout
[[[[MMPReactiveCoreLocation service]
timeout:5.0]
location]
subscribeNext:^(CLLocation *location) {
NSLog(@"[INFO] received single location: %@", location);
}
error:^(NSError *error) {
NSLog(@"[ERROR] error getting location: %@", error);
}
completed:^{
NSLog(@"[INFO] single location signal completed.");
}];
For significant change updates, use significantLocationChanges
signal instead:
// build service, subscribe to 'significantLocationChanges' signal
[[[MMPReactiveCoreLocation service]
significantLocationChanges]
subscribeNext:^(CLLocation *location) {
NSLog(@"[INFO] received location: %@", location);
}];
Just as locations
for constant updates and location
for single update, use significantLocationChanges
for constant significant location change updates and use significantLocationChange
for single significant location change only.
For region monitoring, use region
for adding region to monitor, and regionEvents
to get the signal:
// build service, add 2 regions to monitor, subscribe to region events
[[[[[MMPReactiveCoreLocation service]
region:region1]
region:region2]
regionEvents]
subscribeNext:^(MMPRegionEvent *regionEvent) {
NSLog(@"[INFO] received event: %ld for region: %@", regionEvent.type, regionEvent.region.identifier);
}];
You can also call region
method multiple times to define multiple regions to monitor. See MMPRegionEventType
for more details on what region events are available.
To stop any signals and automatically cleanup the underlying location manager and requests, simply use the stop
method:
self.service = [MMPReactiveCoreLocation service];
// use 'stop' to tell the service that it should stop the subscription. Underlying location manager (CLLocationManager)
// will automatically be stopped and cleaned up if there are no other subscriber.
[self.service stop];
Note that if there are multiple subscribers to signals that use shared underlying location manager (i.e. services built and configured with exactly the same settings), stopping one subscriber may not necessarily stopped the location manager.
Default settings for the location signals are:
- Automatically pauses for location updates. See here.
- Distance filter is kCLDistanceFilterNone.
- Desired accuracy is kCLLocationAccuracyBest.
- Activity type is CLActivityTypeOther.
- On iOS 8, authorization type is "WhenInUse" for
locations
and "Always" forsignificantLocationChanges
andregionEvents
.
Following table shows default authorization that will be used for available signals. You can also manually specify authorization.
Signal | Authorization |
---|---|
locations |
WhenInUse |
location |
WhenInUse |
significantLocationChanges |
Always |
significantLocationChange |
Always |
regionEvents |
Always |
beaconRanges |
WhenInUse |
headingUpdates |
WhenInUse |
visits |
Always |
If you need other than default settings, then you chain-call following methods to set values that you want to customize:
distanceFilter
for setting distance filter.desiredAccuracy
for setting desired accuracy.activityType
for setting activity type.pauseLocationUpdatesAutomatically
orpauseLocationUpdatesManually
to set auto or manual update of location pauses.authorizeAlways
forAlways
authorization andauthorizeWhenInUse
forWhenInUse
authorization.
Here's a sample code on how to customize location manager settings before subscribing to a signal:
MMPReactiveCoreLocation *service = [MMPReactiveCoreLocation service];
RACSignal *locations = [[[[service distanceFilter:kCLDistanceFilterNone]
desiredAccuracy:kCLLocationAccuracyBestForNavigation]
activityType:CLActivityTypeFitness]
locations];
Please see the header file for more setting possibilities.
// handling authorization status change
[[service authorizationStatus] subscribeNext:^(NSNumber *statusNumber) {
CLAuthorizationStatus status = [statusNumber intValue];
switch (status) {
case kCLAuthorizationStatusNotDetermined:
NSLog(@"[INFO] Status changed: kCLAuthorizationStatusNotDetermined");
break;
case kCLAuthorizationStatusRestricted:
NSLog(@"[INFO] Status changed: kCLAuthorizationStatusRestricted");
break;
case kCLAuthorizationStatusDenied:
NSLog(@"[INFO] Status changed: kCLAuthorizationStatusDenied");
break;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
case kCLAuthorizationStatusAuthorizedAlways:
NSLog(@"[INFO] Status changed: kCLAuthorizationStatusAuthorizedAlways");
break;
case kCLAuthorizationStatusAuthorizedWhenInUse:
NSLog(@"[INFO] Status changed: kCLAuthorizationStatusAuthorizedWhenInUse");
break;
#else
case kCLAuthorizationStatusAuthorized:
NSLog(@"[INFO] Status changed: kCLAuthorizationStatusAuthorized");
break;
#endif
default:
break;
}
}];
// handling errors
[[service errors] subscribeNext:^(NSError *error) {
NSLog(@"[ERROR] Location service error: %@", error);
}];
When you need to send request for authorization manually, for example when using MKMapView
and you just need to send the request before setting showsUserLocation
, you can use authorize
method that returns a signal producing status change events (same as authorizationStatus
signal):
[[[[MMPReactiveCoreLocation service]
authorizeAlways]
authorize]
subscribeNext:^(NSNumber *statusNumber) {
CLAuthorizationStatus status = [statusNumber intValue];
switch (status) {
case kCLAuthorizationStatusAuthorizedAlways:
case kCLAuthorizationStatusAuthorizedWhenInUse:
_mapView.showsUserLocation = YES;
break;
case kCLAuthorizationStatusAuthorized:
_mapView.showsUserLocation = YES;
break;
default:
break;
}
}];
Most of the CLLocationManager functionalities including iBeacon, visit monitoring, heading updates, etc. has been implemented but has not been extensively tested so there's bound to be bugs. I'm planning to use this in real world projects so it should be actively maintained. Contributions are welcomed.
I will write more usage samples and documentation as I fix bugs and write tests. In the meantime, if you have any question on how to apply certain CLLocationManager usage pattern using this library, please feel free to contact me or open issues.
- 0.7: CoreBluetooth integration for iBeacon publishing.
- 0.8: Unit tests and documentations.
MMPReactiveCoreLocation is maintained by Mamad Purbo
MMPReactiveCoreLocation is available under the MIT license. See the LICENSE file for more info.