You may find CLLocationManager awkward to use if you adopt RP(RxSwift) paradigm to develop apps. RxLocationManager is an attempt to create a "reactive" skin around CLLocationManager, so that you don't need to worry about things like conform your view controller to CLLocationManagerDelegate which sometimes feels unnatural, where to put CLLocationManager instance(e.g. AppDelegate) for easily referencing, etc. Everything is behind RxLocationManager class and its static methods and variables. Internally RxLocationManager has multiple sharing CLLocationManager+Delegate instances, and manage them efficiently in terms of memory usage and battery life. Instead of providing an "all-in-one" class like CLLocationManager does, RxLocationManager divides properties/methods into several groups based on their relativity, for example, location related APIs go into StandardLocationService class, heading update related APIs go into HeadingUpdateService class, region monitoring related APIs go into RegionMonitoringService class which also includes ranging beacons capability, and visits monitoring related APIs go into MonitoringVisitsService, so it's more clear to use.
Add RxLocationManager dependency to your Podfile
# Podfile
use_frameworks!
# replace YOUR_TARGET_NAME with yours
target 'YOUR_TARGET_NAME' do
pod 'RxLocationManager', '~> 1.0'
end
and run
$ pod install
Add following line to Cartfile
github "popduke/RxLocationManager" ~> 1.0
and run
$ carthage update
- Run following line to add RxLocationManager as a submodule
$ git submodule add [email protected]:popduke/RxLocationManager.git
- Drag
RxLocationManager.xcodeproj
into Project Navigator - Go to
Project > Targets > Build Phases > Link Binary With Libraries
, click+
and selectRxLocationManager [Platform]
targets
đź“Ś Always consult official document of CLLocationManager to learn how to configure it to work in different modes đź“Ś
Add below line to import RxLocationManager module
import RxLocationManager
RxLocationManager.enable
.map{
//$0 is Boolean
return $0 ? "enabled" : "disabled"
}
.subscribeNext{
print("Location Service is \($0)")
}
.addDisposableTo(disposeBag)
RxLocationManager.authorizationStatus
.subscribeNext{
//$0 is CLAuthorizationStatus
print("Current authorization status is \($0)")
}
.addDisposableTo(disposeBag)
//ask for WhenInUse authorization
RxLocationManager.requestWhenInUseAuthorization()
//ask for Always authorization
RxLocationManager.requestAlwaysAuthorization()
#if os(iOS) || os(OSX)
RxLocationManager.significantLocationChangeMonitoringAvailable
RxLocationManager.isMonitoringAvailableForClass(regionClass: AnyClass) -> Bool
#endif
#if os(iOS)
RxLocationManager.deferredLocationUpdatesAvailable
RxLocationManager.headingAvailable
RxLocationManager.isRangingAvailable
#endif
StandardLocationService contains two main Observables: Located and Locating, Located reports only one CLLocation object per subscription and complete, representing the current determined location of device; Locating reports series of CLLocation objects upon observing, representing the changing location of device. Multiple subscriptions share a single underlying CLLocationManager object, RxLocationManager starts location updating when first subscription is made and stops it after last subscription is disposed.
// RxLocationManager.Standard is a shared standard location service instance
#if os(iOS) || os(watchOS) || os(tvOS)
RxLocationManager.Standard.located.subscribe{
event in
switch event{
case .Next(let location):
// the event will only be triggered once to report current determined location of device
print("Current Location is \(location)")
case .Completed:
// completed event will get triggered after location is reported successfully
print("Subscription is Completed")
case .Error(let error):
// in case some error occurred during determining device location, e.g. LocationUnknown
}
}
.addDisposableTo(disposeBag)
#endif
#if os(iOS) || os(OSX)
RxLocationManager.Standard.locating.subscribe{
event in
switch event{
case .Next(let location):
// series of events will be delivered during subscription
print("Current Location is \(location)")
case .Completed:
// no complete event
case .Error(let error):
// LocationUnknown error will be ignored, and other errors reported
}
}
.addDisposableTo(disposeBag)
#endif
Before start subscribing to located or locating, you can also configure the standard location service instance through below chaining style APIs
RxLocationManager.Standard.distanceFilter(distance: CLLocationDistance) -> StandardLocationService
RxLocationManager.Standard.desiredAccuracy(desiredAccuracy: CLLocationAccuracy) -> StandardLocationService
#if os(iOS)
RxLocationManager.Standard.allowsBackgroundLocationUpdates(allow : Bool) -> StandardLocationService
RxLocationManager.Standard.activityType(type: CLActivityType) -> StandardLocationService
#endif
#if os(iOS)
RxLocationManager.Standard.pausesLocationUpdatesAutomatically(true)
RxLocationManager.Standard.isPaused
.map{
//$0 is Boolean
return $0 ? "Paused" : "Resumed"
}
.subscribeNext{
print("Location Updating is \($0)")
}
.addDisposableTo(disposeBag)
#endif
Setup/remove deferred location update condition and observe when condition is satisfied or finished with error
#if os(iOS)
//Setup deferred location update condition
RxLocationManager.Standard.allowDeferredLocationUpdates(untilTraveled:100, timeout: 120)
//Remove current deferred update condition
RxLocationManager.Standard.disallowDeferredLocationUpdates()
//Observe the event when condition is satisfied or finished with error
RxLocationManager.Standard.deferredUpdateFinished
.map{
//$0 is NSError?
return $0 == nil ? "Finished" : "Finished with error code \($0.code) in \($0.domain)"
}
.subscribeNext{
error in
print("Location Updating is \($0)")
}
.addDisposableTo(disposeBag)
#endif
In some cases you need more than one standard location service in your app, which configured differently, you can create a new one by cloning from the shared
var anotherStandardLocationService = RxLocationManager.Standard.clone()
anotherStandardLocationService.distanceFilter(100).desiredAccuracy(50)
SignificantLocationUpdateService contains only one Observable: locating, which reports series of CLLocation objects upon observing, representing the significant location change of device. Multiple subscriptions share a single underlying CLLocationManager object, RxLocationManager starts monitoring significant location change when first subscription is made and stops it after last subscription is disposed.
#if os(iOS) || os(OSX)
// RxLocationManager.SignificantLocation is the shared significant location update service instance
RxLocationManager.SignificantLocation.locating.subscribe{
event in
switch event{
case .Next(let location):
// series of events will be delivered during subscription
print("Current Location is \(location)")
case .Completed:
// no complete event
case .Error(let error):
// in case errors
}
}
.addDisposableTo(disposeBag)
#endif
HeadingUpdateService contains only one Observable: heading, which reports series of CLHeading objects upon observing, representing heading change of device. Multiple subscriptions share a single underlying CLLocationManager object, RxLocationManager starts monitoring device heading change when first subscription is made and stops it after last subscription is disposed.
#if os(iOS)
// RxLocationManager.HeadingUpdate is the shared heading update service instance
RxLocationManager.HeadingUpdate.heading.subscribeNext{
event in
switch event{
case .Next(let heading):
// series of events will be delivered during subscription
print("Current heading is \(heading)")
case .Completed:
// no complete event
case .Error(let error):
// in case errors
}
}
.addDisposableTo(disposeBag)
#endif
Before start subscribing to heading, you can also configure the heading update service instance through below chaining style APIs
#if os(iOS)
RxLocationManager.HeadingUpdate.headingFilter(degrees:CLLocationDegrees) -> HeadingUpdateService
RxLocationManager.HeadingUpdate.headingOrientation(degrees:CLDeviceOrientation) -> HeadingUpdateService
RxLocationManager.HeadingUpdate.displayHeadingCalibration(should:Bool) -> HeadingUpdateService
//Use following to methods to start/stop location updating, so that true heading value will be reported
RxLocationManager.HeadingUpdate.startTrueHeading(withParams:(distanceFilter:CLLocationDistance, desiredAccuracy:CLLocationAccuracy))
RxLocationManager.HeadingUpdate.stopTrueHeading()
#endif
#if os(iOS)
RxLocationManager.HeadingUpdate.dismissHeadingCalibrationDisplay()
#endif
In some cases you need more than one heading update service in your app, which configured differently, you can create a new one by cloning from the shared
var anotherHeadingUpdateService = RxLocationManager.HeadingUpdate.clone()
anotherHeadingUpdateService.distanceFilter(100).desiredAccuracy(50)
#if os(iOS) || os(OSX)
// methods to start|stop monitoring regions
RxLocationManager.RegionMonitoring.startMonitoringForRegions(regions: [CLRegion]) -> RegionMonitoringService
RxLocationManager.RegionMonitoring.stopMonitoringForRegions(regions: [CLRegion]) -> RegionMonitoringService
RxLocationManager.RegionMonitoring.stopMonitoringForAllRegions() -> RegionMonitoringService
RxLocationManager.RegionMonitoring.monitoredRegions.subscribeNext{
//happens no matter when new region is added or existing one gets removed from the monitored regions set
regions in
print("Current monitoring \(regions.count) regions")
}
.addDisposableTo(disposeBag)
#endif
#if os(iOS) || os(OSX)
RxLocationManager.RegionMonitoring.entering.subscribeNext{
region in
print("Device is entering the region: \(region.identifier)")
}
.addDisposableTo(disposeBag)
RxLocationManager.RegionMonitoring.exiting.subscribeNext{
region in
print("Device is leaving the region: \(region.identifier)")
}
.addDisposableTo(disposeBag)
#endif
RxLocationManager.RegionMonitoring.requestRegionsState(regions:[CLRegion]) -> RegionMonitoringService
RxLocationManager.RegionMonitoring.determinedRegionState.subscribeNext{
region, state in
print("the region: \(region.identifier) is in state: \(state.rawValue)")
}
.addDisposableTo(disposeBag)
#if os(iOS)
RxLocationManager.RegionMonitoring.startRangingBeaconsInRegion(region: CLBeaconRegion)
RxLocationManager.RegionMonitoring.stopRangingBeaconsInRegion(region: CLBeaconRegion)
#endif
#if os(iOS)
RxLocationManager.RegionMonitoring.ranging.subscribeNext{
beacons, inRegion in
print("\(beacons.count) beacons ranged in range:\(inRange.identifier)")
}
.addDisposableTo(disposeBag)
#endif
#if os(iOS)
RxLocationManager.VisitMonitoring.startMonitoringVisits()
RxLocationManager.VisitMonitoring.stopMonitoringVisits()
#endif
#if os(iOS)
RxLocationManager.VisitMonitoring.visiting.subscribeNext{
visit in
print("coordinate: \(visit.coordinate.longitude),\(visit.coordinate.latitude)")
}
.addDisposableTo(disposeBag)
#endif