├── .gitignore ├── .swift-version ├── Cartfile ├── Cartfile.private ├── Cartfile.resolved ├── LICENSE.md ├── README.md ├── RxLocationManager.podspec ├── RxLocationManager.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── RxLocationManager iOS.xcscheme │ ├── RxLocationManager macOS.xcscheme │ ├── RxLocationManager tvOS.xcscheme │ └── RxLocationManager watchOS.xcscheme ├── RxLocationManager.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── RxLocationManagerDemo ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── HeadingUpdateServiceViewController.swift ├── Info.plist ├── MonitoredCircleRegionTableViewCell.swift ├── RegionMonitoringServiceViewController.swift ├── RootViewController.swift ├── SignificantLocationUpdateViewController.swift ├── StandardLocationServiceViewController.swift └── VisitMonitoringViewController.swift ├── RxLocationManagerTests ├── Fixtures.swift ├── HeadingUpdateServiceTest.swift ├── Info.plist ├── LocationManagerStub.swift ├── MonitoringVisitsServiceTest.swift ├── RegionMonitoringServiceTest.swift ├── SignificantLocationUpdateServiceTest.swift └── StandardLocationServiceTest.swift └── Sources ├── Bridge.swift ├── HeadingUpdateService.swift ├── Info.plist ├── MonitoringVisitsService.swift ├── RegionMonitoringService.swift ├── RxLocationManager.swift ├── SignificantLocationUpdateService.swift ├── StandardLocationService.swift └── nextId.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ## OS X Finder 2 | .DS_Store 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | 19 | ## Other 20 | *.xccheckout 21 | *.moved-aside 22 | *.xcuserstate 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | # Swift Package Manager 30 | .build/ 31 | 32 | # Carthage 33 | Carthage/Build 34 | Carthage/Checkouts 35 | 36 | .gitmodules 37 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" ~> 5.0 2 | -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" 2 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v8.0.2" 2 | github "ReactiveX/RxSwift" "5.0.1" 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | **The MIT License** 2 | **Copyright © 2016 Yonny Hao** 3 | **All rights reserved.** 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Reactive LocationManager in Swift 2 | 3 | [![CocoaPods Compatible](https://img.shields.io/badge/cocoapod-v1.0.1-brightgreen.svg)](https://cocoapods.org) 4 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage) 5 | [![Platform](https://img.shields.io/badge/Platform-%20iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS-lightgrey.svg)](https://github.com/popduke/RxLocationManager) 6 | 7 | ## Introduction 8 | You may find CLLocationManager awkward to use if you adopt [RP](http://reactivex.io/)([RxSwift](https://github.com/ReactiveX/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. 9 | 10 | ## Requirement 11 | * iOS 8.0+ | macOS 10.10+ | tvOS 9.0+ | watchOS 2.0+ 12 | * Xcode 8.1+ 13 | * Swift 3.0+ (for Swift 2 support, see branch [Swift-2.X](https://github.com/popduke/RxLocationManager/tree/Swift-2.x)) 14 | * RxSwift 3.0+ 15 | 16 | ## Installation 17 | ### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) 18 | Add RxLocationManager dependency to your Podfile 19 | ``` 20 | # Podfile 21 | use_frameworks! 22 | 23 | # replace YOUR_TARGET_NAME with yours 24 | target 'YOUR_TARGET_NAME' do 25 | pod 'RxLocationManager', '~> 2.0' 26 | end 27 | ``` 28 | and run 29 | ``` 30 | $ pod install 31 | ``` 32 | 33 | ### [Carthage](https://github.com/Carthage/Carthage) 34 | Add following line to `Cartfile` 35 | 36 | ``` 37 | github "popduke/RxLocationManager" ~> 2.0 38 | ``` 39 | and run 40 | 41 | ``` 42 | $ carthage update 43 | ``` 44 | ### [Git submodules](https://git-scm.com/docs/git-submodule) 45 | * Run following line to add RxLocationManager as a submodule 46 | 47 | ``` 48 | $ git submodule add git@github.com:popduke/RxLocationManager.git 49 | ``` 50 | 51 | * Drag `RxLocationManager.xcodeproj` into Project Navigator 52 | * Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxLocationManager [Platform]` targets 53 | 54 | ## Usage 55 | **:pushpin: Always consult official document of [*CLLocationManager*](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/cl/CLLocationManager) to learn how to configure it to work in different modes :pushpin:** 56 | 57 | Add below line to import RxLocationManager module 58 | ``` 59 | import RxLocationManager 60 | ``` 61 | 62 | ### Observe Location Service's enable/disable status change 63 | 64 | ``` 65 | RxLocationManager.enable 66 | .map{ 67 | //$0 is Boolean 68 | return $0 ? "enabled" : "disabled" 69 | } 70 | .subscribe(onNext:{ 71 | print("Location Service is \($0)") 72 | }) 73 | .addDisposableTo(disposeBag) 74 | ``` 75 | 76 | ### Observe app's authorization status change 77 | 78 | ``` 79 | RxLocationManager.authorizationStatus 80 | .subscribe(onNext:{ 81 | //$0 is CLAuthorizationStatus 82 | print("Current authorization status is \($0)") 83 | }) 84 | .addDisposableTo(disposeBag) 85 | ``` 86 | 87 | ### Request authorization to your app 88 | ``` 89 | //ask for WhenInUse authorization 90 | RxLocationManager.requestWhenInUseAuthorization() 91 | //ask for Always authorization 92 | RxLocationManager.requestAlwaysAuthorization() 93 | ``` 94 | 95 | ### Determine service availability 96 | ``` 97 | #if os(iOS) || os(OSX) 98 | RxLocationManager.significantLocationChangeMonitoringAvailable 99 | RxLocationManager.isMonitoringAvailableForClass(regionClass: AnyClass) -> Bool 100 | #endif 101 | 102 | #if os(iOS) 103 | RxLocationManager.deferredLocationUpdatesAvailable 104 | RxLocationManager.headingAvailable 105 | RxLocationManager.isRangingAvailable 106 | #endif 107 | ``` 108 | 109 | ### Standard Location Service 110 | *StandardLocationService* contains two main *Observable*s: *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. 111 | #### Determine current location of device 112 | 113 | ``` 114 | // RxLocationManager.Standard is a shared standard location service instance 115 | #if os(iOS) || os(watchOS) || os(tvOS) 116 | RxLocationManager.Standard.located.subscribe( 117 | onNext:{ 118 | // the event will only be triggered once to report current determined location of device 119 | print("Current Location is \($0)") 120 | }, 121 | onError:{ 122 | // in case some error occurred during determining device location, e.g. LocationUnknown 123 | }, 124 | onCompleted:{ 125 | // completed event will get triggered after location is reported successfully 126 | print("Subscription is Completed") 127 | }) 128 | .addDisposableTo(disposeBag) 129 | #endif 130 | ``` 131 | 132 | #### Monitoring location update of device 133 | ``` 134 | #if os(iOS) || os(OSX) || os(watchOS) 135 | //available in watchOS 3.0 136 | RxLocationManager.Standard.locating.subscribe( 137 | onNext:{ 138 | // series of events will be delivered during subscription 139 | print("Current Location is \($0)") 140 | }, 141 | onError:{ 142 | // LocationUnknown error will be ignored, and other errors reported 143 | }, 144 | onCompleted:{ 145 | // no complete event 146 | }) 147 | .addDisposableTo(disposeBag) 148 | #endif 149 | ``` 150 | #### Configuration 151 | Before start subscribing to *located* or *locating*, you can also configure the standard location service instance through below chaining style APIs 152 | ``` 153 | RxLocationManager.Standard.distanceFilter(distance: CLLocationDistance) -> StandardLocationService 154 | RxLocationManager.Standard.desiredAccuracy(desiredAccuracy: CLLocationAccuracy) -> StandardLocationService 155 | 156 | #if os(iOS) 157 | RxLocationManager.Standard.allowsBackgroundLocationUpdates(allow : Bool) -> StandardLocationService 158 | RxLocationManager.Standard.activityType(type: CLActivityType) -> StandardLocationService 159 | #endif 160 | ``` 161 | 162 | #### Enable auto-paused mode for location delivery, and observe the notification of pausing state change 163 | ``` 164 | #if os(iOS) 165 | RxLocationManager.Standard.pausesLocationUpdatesAutomatically(true) 166 | 167 | RxLocationManager.Standard.isPaused 168 | .map{ 169 | //$0 is Boolean 170 | return $0 ? "Paused" : "Resumed" 171 | } 172 | .subscribe( 173 | onNext:{ 174 | print("Location Updating is \($0)") 175 | } 176 | ) 177 | .addDisposableTo(disposeBag) 178 | #endif 179 | ``` 180 | 181 | #### Setup/remove deferred location update condition and observe when condition is satisfied or finished with error 182 | ``` 183 | #if os(iOS) 184 | //Setup deferred location update condition 185 | RxLocationManager.Standard.allowDeferredLocationUpdates(untilTraveled:100, timeout: 120) 186 | 187 | //Remove current deferred update condition 188 | RxLocationManager.Standard.disallowDeferredLocationUpdates() 189 | 190 | //Observe the event when condition is satisfied or finished with error 191 | RxLocationManager.Standard.deferredUpdateFinished 192 | .map{ 193 | //$0 is NSError? 194 | return $0 == nil ? "Finished" : "Finished with error code \($0.code) in \($0.domain)" 195 | } 196 | .subscribe( 197 | onNext:{ 198 | error in 199 | print("Location Updating is \($0)") 200 | } 201 | ) 202 | .addDisposableTo(disposeBag) 203 | #endif 204 | ``` 205 | 206 | #### Multiple standard location services 207 | 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 208 | ``` 209 | var anotherStandardLocationService = RxLocationManager.Standard.clone() 210 | anotherStandardLocationService.distanceFilter(100).desiredAccuracy(50) 211 | ``` 212 | 213 | ### Significant Location Update Service 214 | 215 | *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. 216 | ``` 217 | #if os(iOS) || os(OSX) 218 | // RxLocationManager.SignificantLocation is the shared significant location update service instance 219 | RxLocationManager.SignificantLocation.locating.subscribe( 220 | onNext:{ 221 | print("Current Location is \($0)") 222 | }, 223 | onError:{ 224 | // in case errors 225 | }, 226 | onCompleted:{ 227 | // no complete event 228 | } 229 | ) 230 | .addDisposableTo(disposeBag) 231 | #endif 232 | ``` 233 | 234 | ### Heading Update Service 235 | 236 | *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. 237 | 238 | #### Observe heading change of device 239 | ``` 240 | #if os(iOS) 241 | // RxLocationManager.HeadingUpdate is the shared heading update service instance 242 | RxLocationManager.HeadingUpdate.heading.subscribe( 243 | onNext:{ 244 | // series of events will be delivered during subscription 245 | print("Current heading is \($0)") 246 | }, 247 | onCompleted:{ 248 | // no complete event 249 | }, 250 | onError:{ 251 | // in case errors 252 | } 253 | ) 254 | .addDisposableTo(disposeBag) 255 | #endif 256 | ``` 257 | 258 | #### Configuration 259 | Before start subscribing to *heading*, you can also configure the heading update service instance through below chaining style APIs 260 | ``` 261 | #if os(iOS) 262 | RxLocationManager.HeadingUpdate.headingFilter(degrees:CLLocationDegrees) -> HeadingUpdateService 263 | RxLocationManager.HeadingUpdate.headingOrientation(degrees:CLDeviceOrientation) -> HeadingUpdateService 264 | RxLocationManager.HeadingUpdate.displayHeadingCalibration(should:Bool) -> HeadingUpdateService 265 | 266 | //Use following to methods to start/stop location updating, so that true heading value will be reported 267 | RxLocationManager.HeadingUpdate.startTrueHeading(withParams:(distanceFilter:CLLocationDistance, desiredAccuracy:CLLocationAccuracy)) 268 | RxLocationManager.HeadingUpdate.stopTrueHeading() 269 | #endif 270 | ``` 271 | 272 | #### Dismiss heading calibration display if any 273 | ``` 274 | #if os(iOS) 275 | RxLocationManager.HeadingUpdate.dismissHeadingCalibrationDisplay() 276 | #endif 277 | ``` 278 | 279 | #### Multiple heading update services 280 | 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 281 | ``` 282 | var anotherHeadingUpdateService = RxLocationManager.HeadingUpdate.clone() 283 | anotherHeadingUpdateService.distanceFilter(100).desiredAccuracy(50) 284 | ``` 285 | ### Region Monitoring Service 286 | 287 | #### Observe the changes to the collection of current monitored regions 288 | ``` 289 | #if os(iOS) || os(OSX) 290 | // methods to start|stop monitoring regions 291 | RxLocationManager.RegionMonitoring.startMonitoringForRegions(regions: [CLRegion]) -> RegionMonitoringService 292 | RxLocationManager.RegionMonitoring.stopMonitoringForRegions(regions: [CLRegion]) -> RegionMonitoringService 293 | RxLocationManager.RegionMonitoring.stopMonitoringForAllRegions() -> RegionMonitoringService 294 | 295 | RxLocationManager.RegionMonitoring.monitoredRegions.subscribe( 296 | onNext:{ 297 | //happens no matter when new region is added or existing one gets removed from the monitored regions set 298 | regions in 299 | print("Current monitoring \(regions.count) regions") 300 | } 301 | ) 302 | .addDisposableTo(disposeBag) 303 | #endif 304 | ``` 305 | 306 | #### Observe region enter/exit event 307 | ``` 308 | #if os(iOS) || os(OSX) 309 | RxLocationManager.RegionMonitoring.entering.subscribe( 310 | onNext:{ 311 | region in 312 | print("Device is entering the region: \(region.identifier)") 313 | } 314 | ) 315 | .addDisposableTo(disposeBag) 316 | 317 | RxLocationManager.RegionMonitoring.exiting.subscribe( 318 | onNext:{ 319 | region in 320 | print("Device is leaving the region: \(region.identifier)") 321 | } 322 | ) 323 | .addDisposableTo(disposeBag) 324 | #endif 325 | ``` 326 | 327 | #### Ask for the current state of monitored regions 328 | ``` 329 | RxLocationManager.RegionMonitoring.requestRegionsState(regions:[CLRegion]) -> RegionMonitoringService 330 | RxLocationManager.RegionMonitoring.determinedRegionState.subscribe( 331 | onNext:{ 332 | region, state in 333 | print("the region: \(region.identifier) is in state: \(state.rawValue)") 334 | } 335 | ) 336 | .addDisposableTo(disposeBag) 337 | ``` 338 | 339 | #### Start/stop ranging beacons in range 340 | ``` 341 | #if os(iOS) 342 | RxLocationManager.RegionMonitoring.startRangingBeaconsInRegion(region: CLBeaconRegion) 343 | RxLocationManager.RegionMonitoring.stopRangingBeaconsInRegion(region: CLBeaconRegion) 344 | #endif 345 | ``` 346 | 347 | #### Observe ranged beacons 348 | ``` 349 | #if os(iOS) 350 | RxLocationManager.RegionMonitoring.ranging.subscribe( 351 | onNext:{ 352 | beacons, inRegion in 353 | print("\(beacons.count) beacons ranged in range:\(inRange.identifier)") 354 | } 355 | ) 356 | .addDisposableTo(disposeBag) 357 | #endif 358 | ``` 359 | 360 | ### Monitoring Visits Service 361 | 362 | #### Start/stop monitoring visits 363 | ``` 364 | #if os(iOS) 365 | RxLocationManager.VisitMonitoring.startMonitoringVisits() 366 | RxLocationManager.VisitMonitoring.stopMonitoringVisits() 367 | #endif 368 | ``` 369 | 370 | #### Observe visit events 371 | ``` 372 | #if os(iOS) 373 | RxLocationManager.VisitMonitoring.visiting.subscribe( 374 | onNext:{ 375 | visit in 376 | print("coordinate: \(visit.coordinate.longitude),\(visit.coordinate.latitude)") 377 | } 378 | ) 379 | .addDisposableTo(disposeBag) 380 | #endif 381 | ``` 382 | 383 | ## MIT License 384 | -------------------------------------------------------------------------------- /RxLocationManager.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "RxLocationManager" 3 | spec.version = "3.0.1" 4 | spec.summary = "Reactive Style Location Manager for iOS, macOS, watchOS, tvOS" 5 | spec.description = "If you programs in functional reactive style in iOS, RxLocationManager makes location management a lot easier comparing to CLLocationManager" 6 | spec.homepage = "https://github.com/popduke/RxLocationManager" 7 | spec.license = { type: 'MIT', file: 'LICENSE.md' } 8 | spec.authors = { "Yonny Hao" => 'popduke@gmail.com' } 9 | 10 | spec.ios.deployment_target = '8.0' 11 | spec.osx.deployment_target = '10.10' 12 | spec.watchos.deployment_target = '2.0' 13 | spec.tvos.deployment_target = '9.0' 14 | 15 | spec.frameworks = "Foundation", "CoreLocation" 16 | spec.requires_arc = true 17 | spec.source = { git: "https://github.com/popduke/RxLocationManager.git", tag: spec.version.to_s } 18 | spec.source_files = 'sources/*.{h,swift}' 19 | 20 | spec.dependency "RxSwift", "~> 5.0" 21 | end 22 | -------------------------------------------------------------------------------- /RxLocationManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RxLocationManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RxLocationManager.xcodeproj/xcshareddata/xcschemes/RxLocationManager iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 75 | 81 | 82 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /RxLocationManager.xcodeproj/xcshareddata/xcschemes/RxLocationManager macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /RxLocationManager.xcodeproj/xcshareddata/xcschemes/RxLocationManager tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /RxLocationManager.xcodeproj/xcshareddata/xcschemes/RxLocationManager watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /RxLocationManager.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /RxLocationManager.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RxLocationManagerDemo 4 | // 5 | // Created by Yonny Hao on 16/7/10. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | 12 | extension NSError{ 13 | open override var description: String{ 14 | get{ 15 | switch domain { 16 | case "kCLErrorDomain": 17 | switch CLError(_nsError:self).code { 18 | case .locationUnknown: 19 | return "Location Unknown" 20 | case .denied: 21 | return "Denied" 22 | case .network: 23 | return "Network" 24 | case .headingFailure: 25 | return "Heading Failure" 26 | case .regionMonitoringDenied: 27 | return "Region Monitoring Denied" 28 | case .regionMonitoringFailure: 29 | return "Region Monitoring Failure" 30 | case .regionMonitoringSetupDelayed: 31 | return "Region Monitoring Setup Delayed" 32 | case .regionMonitoringResponseDelayed: 33 | return "Region Monitoring Response Delayed" 34 | case .geocodeFoundNoResult: 35 | return "Geocode Found No Result" 36 | case .geocodeFoundPartialResult: 37 | return "Geocode Found Partial Result" 38 | case .geocodeCanceled: 39 | return "Geocode Canceled" 40 | case .deferredFailed: 41 | return "Deferred Failed" 42 | case .deferredNotUpdatingLocation: 43 | return "Deferred Not Updating Location" 44 | case .deferredAccuracyTooLow: 45 | return "Deferred Accuracy Too Low" 46 | case .deferredDistanceFiltered: 47 | return "Deferred Distance Filtered" 48 | case .deferredCanceled: 49 | return "Deferred Canceled" 50 | case .rangingUnavailable: 51 | return "Ranging Unavailable" 52 | case .rangingFailure: 53 | return "Ranging Failure" 54 | } 55 | default: 56 | return self.description 57 | } 58 | } 59 | } 60 | } 61 | 62 | @UIApplicationMain 63 | class AppDelegate: UIResponder, UIApplicationDelegate { 64 | 65 | var window: UIWindow? 66 | 67 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 68 | // Override point for customization after application launch. 69 | return true 70 | } 71 | 72 | func applicationWillResignActive(_ application: UIApplication) { 73 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 74 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 75 | } 76 | 77 | func applicationDidEnterBackground(_ application: UIApplication) { 78 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 79 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 80 | } 81 | 82 | func applicationWillEnterForeground(_ application: UIApplication) { 83 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 84 | } 85 | 86 | func applicationDidBecomeActive(_ application: UIApplication) { 87 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 88 | } 89 | 90 | func applicationWillTerminate(_ application: UIApplication) { 91 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 92 | } 93 | 94 | 95 | } 96 | 97 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /RxLocationManagerDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/HeadingUpdateServiceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeadingUpdateServiceViewController.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/13. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | import RxSwift 12 | import RxLocationManager 13 | 14 | class HeadingUpdateServiceViewController: UIViewController { 15 | 16 | @IBOutlet weak var magneticHeadingValueLbl: UILabel! 17 | 18 | @IBOutlet weak var trueHeadingValueLbl: UILabel! 19 | 20 | @IBOutlet weak var headingAccuracyValueLbl: UILabel! 21 | 22 | @IBOutlet weak var timestampValueLbl: UILabel! 23 | 24 | @IBOutlet weak var toggleHeadingUpdateBtn: UIButton! 25 | 26 | @IBOutlet weak var trueHeadingSwitch: UISwitch! 27 | 28 | private var disposeBag: DisposeBag! 29 | 30 | private var headingSubscription: Disposable? 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | } 35 | 36 | override func viewWillAppear(_ animated: Bool) { 37 | disposeBag = DisposeBag() 38 | trueHeadingSwitch.rx.value 39 | .subscribe(onNext:{ 40 | if $0{ 41 | RxLocationManager.HeadingUpdate.startTrueHeading(nil) 42 | }else{ 43 | RxLocationManager.HeadingUpdate.stopTrueHeading() 44 | } 45 | }) 46 | .addDisposableTo(disposeBag) 47 | 48 | toggleHeadingUpdateBtn.rx.tap 49 | .subscribe{ 50 | [unowned self] 51 | _ in 52 | if self.headingSubscription == nil { 53 | self.toggleHeadingUpdateBtn.setTitle("Stop", for: .normal) 54 | self.headingSubscription = RxLocationManager.HeadingUpdate.heading 55 | .subscribe(onNext:{ 56 | [unowned self] 57 | heading in 58 | self.magneticHeadingValueLbl.text = heading.magneticHeading.description 59 | self.trueHeadingValueLbl.text = heading.trueHeading.description 60 | self.headingAccuracyValueLbl.text = heading.headingAccuracy.description 61 | self.timestampValueLbl.text = heading.timestamp.description 62 | }) 63 | }else{ 64 | self.headingSubscription?.dispose() 65 | self.toggleHeadingUpdateBtn.setTitle("Start", for: .normal) 66 | self.magneticHeadingValueLbl.text = "" 67 | self.trueHeadingValueLbl.text = "" 68 | self.headingAccuracyValueLbl.text = "" 69 | self.timestampValueLbl.text = "" 70 | self.headingSubscription!.dispose() 71 | self.headingSubscription = nil 72 | } 73 | } 74 | .addDisposableTo(disposeBag) 75 | } 76 | 77 | override func viewDidDisappear(_ animated: Bool) { 78 | disposeBag = nil 79 | headingSubscription?.dispose() 80 | headingSubscription = nil 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSLocationAlwaysUsageDescription 26 | RxLocationManagerDemo app needs authorization all the time 27 | NSLocationWhenInUseUsageDescription 28 | RxLocationManagerDemo app requests authorization when in use 29 | UIBackgroundModes 30 | 31 | location 32 | 33 | UILaunchStoryboardName 34 | LaunchScreen 35 | UIMainStoryboardFile 36 | Main 37 | UIRequiredDeviceCapabilities 38 | 39 | armv7 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/MonitoredCircleRegionTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonitoredCircleRegionTableViewCell.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/14. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | 12 | class MonitoredCircleRegionTableViewCell: UITableViewCell { 13 | @IBOutlet weak var centerCoordLbl: UILabel! 14 | 15 | @IBOutlet weak var inoutStatusLbl: UILabel! 16 | 17 | var monitoredRegion: CLCircularRegion?{ 18 | didSet{ 19 | if let region = monitoredRegion{ 20 | centerCoordLbl.text = "\(region.center.latitude),\(region.center.longitude)" 21 | } 22 | } 23 | } 24 | 25 | override func awakeFromNib() { 26 | super.awakeFromNib() 27 | // Initialization code 28 | } 29 | 30 | override func setSelected(_ selected: Bool, animated: Bool) { 31 | super.setSelected(selected, animated: animated) 32 | 33 | // Configure the view for the selected state 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/RegionMonitoringServiceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegionMonitoringServiceViewController.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/14. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | import RxLocationManager 12 | import RxSwift 13 | 14 | extension CLRegionState: CustomStringConvertible{ 15 | public var description:String{ 16 | get{ 17 | switch self{ 18 | case .unknown: 19 | return "Unknown" 20 | case .inside: 21 | return "IN" 22 | case .outside: 23 | return "OUT" 24 | } 25 | } 26 | } 27 | } 28 | 29 | class RegionMonitoringServiceViewController: UIViewController { 30 | 31 | @IBOutlet weak var addRegionBtn: UIButton! 32 | 33 | @IBOutlet weak var errorLbl: UILabel! 34 | 35 | @IBOutlet weak var monitoredRangesTableView: UITableView! 36 | 37 | private var disposeBag:DisposeBag! 38 | 39 | override func viewWillAppear(_ animated: Bool) { 40 | disposeBag = DisposeBag() 41 | addRegionBtn.rx.tap 42 | .subscribe{ 43 | [unowned self] 44 | _ in 45 | self.errorLbl.text = "" 46 | RxLocationManager.Standard.located 47 | .do(onError:{ 48 | self.errorLbl.text = ($0 as NSError).description 49 | }) 50 | .subscribe(onNext:{ 51 | location in 52 | _ = RxLocationManager.RegionMonitoring.startMonitoringForRegions([CLCircularRegion(center: location.coordinate, radius: 20, identifier: location.timestamp.description)]) 53 | }) 54 | .addDisposableTo(self.disposeBag) 55 | } 56 | .addDisposableTo(disposeBag) 57 | 58 | RxLocationManager.RegionMonitoring.error 59 | .subscribe(onNext:{ 60 | [unowned self] 61 | region, error in 62 | self.errorLbl.text = error.description 63 | }) 64 | .addDisposableTo(disposeBag) 65 | 66 | RxLocationManager.RegionMonitoring.monitoredRegions 67 | .bindTo(monitoredRangesTableView.rx.items) { (tableView, row, monitoredRegion) in 68 | let cell = tableView.dequeueReusableCell(withIdentifier: "MonitoredRegionTableViewCell")! as! MonitoredCircleRegionTableViewCell 69 | cell.monitoredRegion = monitoredRegion as? CLCircularRegion 70 | return cell 71 | } 72 | .addDisposableTo(disposeBag) 73 | 74 | monitoredRangesTableView.rx.itemDeleted 75 | .subscribe(onNext:{ 76 | [unowned self] 77 | indexOfRemovedRegion in 78 | let removedRegionCell = self.monitoredRangesTableView.cellForRow(at: indexOfRemovedRegion) as! MonitoredCircleRegionTableViewCell 79 | _ = RxLocationManager.RegionMonitoring.stopMonitoringForRegions([removedRegionCell.monitoredRegion!]) 80 | }) 81 | .addDisposableTo(disposeBag) 82 | 83 | RxLocationManager.RegionMonitoring.entering 84 | .subscribe(onNext:{ 85 | [unowned self] 86 | enteredRegion in 87 | for i in 0 ..< self.monitoredRangesTableView.numberOfSections { 88 | for j in 0 ..< self.monitoredRangesTableView.numberOfRows(inSection: i){ 89 | if let cell = self.monitoredRangesTableView.cellForRow(at:IndexPath(row: j, section: i)){ 90 | let monitoredCell = cell as! MonitoredCircleRegionTableViewCell 91 | if monitoredCell.monitoredRegion!.identifier == enteredRegion.identifier{ 92 | monitoredCell.inoutStatusLbl!.text = "IN" 93 | } 94 | } 95 | } 96 | } 97 | }) 98 | .addDisposableTo(disposeBag) 99 | 100 | RxLocationManager.RegionMonitoring.exiting 101 | .subscribe(onNext:{ 102 | [unowned self] 103 | exitedRegion in 104 | for i in 0 ..< self.monitoredRangesTableView.numberOfSections { 105 | for j in 0 ..< self.monitoredRangesTableView.numberOfRows(inSection: i){ 106 | if let cell = self.monitoredRangesTableView.cellForRow(at:IndexPath(row: j, section: i)){ 107 | let monitoredCell = cell as! MonitoredCircleRegionTableViewCell 108 | if monitoredCell.monitoredRegion!.identifier == exitedRegion.identifier{ 109 | monitoredCell.inoutStatusLbl!.text = "OUT" 110 | } 111 | } 112 | } 113 | } 114 | }) 115 | .addDisposableTo(disposeBag) 116 | 117 | monitoredRangesTableView.rx.itemSelected 118 | .subscribe(onNext:{ 119 | [unowned self] 120 | indexPath in 121 | let monitoredCell = self.monitoredRangesTableView.cellForRow(at:indexPath) as! MonitoredCircleRegionTableViewCell 122 | _ = RxLocationManager.RegionMonitoring.requestRegionsState([monitoredCell.monitoredRegion!]) 123 | }) 124 | .addDisposableTo(disposeBag) 125 | 126 | RxLocationManager.RegionMonitoring.determinedRegionState 127 | .subscribe(onNext:{ 128 | [unowned self] 129 | region, state in 130 | for i in 0 ..< self.monitoredRangesTableView.numberOfSections { 131 | for j in 0 ..< self.monitoredRangesTableView.numberOfRows(inSection: i){ 132 | if let cell = self.monitoredRangesTableView.cellForRow(at: IndexPath(row: j, section: i)){ 133 | let monitoredCell = cell as! MonitoredCircleRegionTableViewCell 134 | if monitoredCell.monitoredRegion!.identifier == region.identifier{ 135 | monitoredCell.inoutStatusLbl!.text = state.description 136 | } 137 | } 138 | } 139 | } 140 | }) 141 | .addDisposableTo(disposeBag) 142 | } 143 | 144 | override func viewDidDisappear(_ animated: Bool) { 145 | disposeBag = nil 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/RootViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // RxLocationManagerDemo 4 | // 5 | // Created by Yonny Hao on 16/7/10. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | import RxSwift 12 | import RxCocoa 13 | import RxLocationManager 14 | 15 | class RootViewController: UIViewController { 16 | var disposeBag = DisposeBag() 17 | 18 | @IBOutlet weak var requestWhenInUseBtn: UIButton! 19 | 20 | @IBOutlet weak var requestAlwaysBtn: UIButton! 21 | 22 | @IBOutlet weak var standardLocationServiceBtn: UIButton! 23 | 24 | @IBOutlet weak var locationServiceStatusLbl: UILabel! 25 | 26 | @IBOutlet weak var significantLocationUpdateBtn: UIButton! 27 | 28 | @IBOutlet weak var headingUpdateServiceBtn: UIButton! 29 | 30 | @IBOutlet weak var regionMonitoringServiceBtn: UIButton! 31 | 32 | @IBOutlet weak var authStatusLbl: UILabel! 33 | 34 | @IBOutlet weak var visitMonitoringServiceBtn: UIButton! 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | let isAuthorized = RxLocationManager.authorizationStatus.map{return $0 == .authorizedAlways || $0 == .authorizedWhenInUse} 39 | 40 | isAuthorized.subscribe(standardLocationServiceBtn.rx.isEnabled).addDisposableTo(disposeBag) 41 | isAuthorized.subscribe(visitMonitoringServiceBtn.rx.isEnabled).addDisposableTo(disposeBag) 42 | 43 | isAuthorized.map{ 44 | $0 && RxLocationManager.significantLocationChangeMonitoringAvailable 45 | } 46 | .bindTo(significantLocationUpdateBtn.rx.isEnabled) 47 | .addDisposableTo(disposeBag) 48 | 49 | isAuthorized.map{ 50 | $0 && RxLocationManager.headingAvailable 51 | } 52 | .bindTo(headingUpdateServiceBtn.rx.isEnabled) 53 | .addDisposableTo(disposeBag) 54 | 55 | isAuthorized.map{ 56 | $0 && RxLocationManager.isMonitoringAvailableForClass(regionClass: CLCircularRegion.self) 57 | } 58 | .bindTo(regionMonitoringServiceBtn.rx.isEnabled) 59 | .addDisposableTo(disposeBag) 60 | 61 | requestWhenInUseBtn.rx.tap 62 | .subscribe( 63 | onNext:{ 64 | _ in 65 | RxLocationManager.requestWhenInUseAuthorization() 66 | }) 67 | .addDisposableTo(disposeBag) 68 | 69 | requestAlwaysBtn.rx.tap 70 | .subscribe( 71 | onNext:{ 72 | _ in 73 | RxLocationManager.requestAlwaysAuthorization() 74 | }) 75 | .addDisposableTo(disposeBag) 76 | 77 | RxLocationManager.enabled 78 | .map{return "Location Service is \($0 ? "ON":"OFF")"} 79 | .bindTo(locationServiceStatusLbl.rx.text) 80 | .addDisposableTo(disposeBag) 81 | 82 | RxLocationManager.authorizationStatus 83 | .map { 84 | switch($0){ 85 | case .notDetermined: 86 | return "NotDetermined" 87 | case .restricted: 88 | return "Restricted" 89 | case .denied: 90 | return "Denied" 91 | case .authorizedAlways: 92 | return "AuthorizedAlways" 93 | case .authorizedWhenInUse: 94 | return "AuthorizedWhenInUse" 95 | } 96 | } 97 | .map { 98 | return "Authorization Status is " + $0 99 | } 100 | .bindTo(authStatusLbl.rx.text) 101 | .addDisposableTo(disposeBag) 102 | 103 | } 104 | 105 | override func didReceiveMemoryWarning() { 106 | super.didReceiveMemoryWarning() 107 | // Dispose of any resources that can be recreated. 108 | } 109 | 110 | 111 | } 112 | 113 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/SignificantLocationUpdateViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignificantLocationUpdateViewController.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/12. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxLocationManager 11 | import RxSwift 12 | import RxCocoa 13 | 14 | class SignificantLocationUpdateViewController: UIViewController { 15 | 16 | @IBOutlet weak var currentLocationLbl: UILabel! 17 | 18 | @IBOutlet weak var toggleSignificantLocationUpdateBtn: UIButton! 19 | 20 | private var disposeBag:DisposeBag! 21 | 22 | private var locatingSubscription: Disposable? 23 | override func viewWillAppear(_ animated: Bool) { 24 | disposeBag = DisposeBag() 25 | toggleSignificantLocationUpdateBtn.rx.tap 26 | .subscribe{ 27 | [unowned self] 28 | _ in 29 | if self.locatingSubscription == nil { 30 | self.toggleSignificantLocationUpdateBtn.setTitle("Stop", for: .normal) 31 | self.locatingSubscription = RxLocationManager.SignificantLocation.locating 32 | .map{ 33 | let coord = $0.last!; 34 | return "\(coord.coordinate.latitude),\(coord.coordinate.longitude)" 35 | } 36 | .catchErrorJustReturn("") 37 | .subscribe(self.currentLocationLbl.rx.text) 38 | }else{ 39 | self.toggleSignificantLocationUpdateBtn.setTitle("Start", for: .normal) 40 | self.currentLocationLbl.text = "" 41 | self.locatingSubscription!.dispose() 42 | self.locatingSubscription = nil 43 | } 44 | } 45 | .addDisposableTo(disposeBag) 46 | } 47 | override func viewDidDisappear(_ animated: Bool) { 48 | disposeBag = nil 49 | locatingSubscription?.dispose() 50 | locatingSubscription = nil 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/StandardLocationServiceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardLocationServiceViewController.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/10. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxLocationManager 11 | import RxSwift 12 | import RxCocoa 13 | 14 | class StandardLocationServiceViewController: UIViewController { 15 | 16 | @IBOutlet weak var currentLocationLbl: UILabel! 17 | 18 | @IBOutlet weak var errorLbl: UILabel! 19 | 20 | @IBOutlet weak var getCurrentLocationBtn: UIButton! 21 | 22 | @IBOutlet weak var toggleLocatingBtn: UIButton! 23 | 24 | @IBOutlet weak var modeSwitcher: UISegmentedControl! 25 | 26 | private var disposeBag:DisposeBag! 27 | 28 | private var locatedSubscription: Disposable? 29 | private var locatingSubscription: Disposable? 30 | 31 | override func viewWillAppear(_ animated: Bool) { 32 | disposeBag = DisposeBag() 33 | modeSwitcher.rx.value 34 | .map{ 35 | return $0 != 0 36 | } 37 | .subscribe(getCurrentLocationBtn.rx.isHidden) 38 | .addDisposableTo(disposeBag) 39 | 40 | modeSwitcher.rx.value 41 | .map{ 42 | return $0 == 0 43 | } 44 | .subscribe(toggleLocatingBtn.rx.isHidden) 45 | .addDisposableTo(disposeBag) 46 | 47 | getCurrentLocationBtn.rx.tap 48 | .subscribe{ 49 | [unowned self] 50 | _ in 51 | if self.locatedSubscription != nil { 52 | self.currentLocationLbl.text = "" 53 | self.locatedSubscription!.dispose() 54 | } 55 | self.locatedSubscription = RxLocationManager.Standard.located 56 | .map{ 57 | return "\($0.coordinate.latitude),\($0.coordinate.longitude)" 58 | } 59 | .do( 60 | onNext:{ 61 | _ in 62 | self.errorLbl.text = "" 63 | }, 64 | onError:{ 65 | self.currentLocationLbl.text = "" 66 | self.errorLbl.text = ($0 as NSError).description 67 | } 68 | ) 69 | .catchErrorJustReturn("") 70 | .bindTo(self.currentLocationLbl.rx.text) 71 | } 72 | .addDisposableTo(disposeBag) 73 | 74 | toggleLocatingBtn.rx.tap 75 | .subscribe{ 76 | [unowned self] 77 | _ in 78 | if self.locatingSubscription == nil { 79 | self.toggleLocatingBtn.setTitle("Stop", for: .normal) 80 | self.locatingSubscription = RxLocationManager.Standard.locating 81 | .map{ 82 | let coord = $0.last!; 83 | return "\(coord.coordinate.latitude),\(coord.coordinate.longitude)" 84 | } 85 | .do( 86 | onNext:{ 87 | _ in 88 | self.errorLbl.text = "" 89 | }, 90 | onError:{ 91 | self.currentLocationLbl.text = "" 92 | self.errorLbl.text = ($0 as NSError).description 93 | } 94 | ) 95 | .catchErrorJustReturn("") 96 | .bindTo(self.currentLocationLbl.rx.text) 97 | }else{ 98 | self.toggleLocatingBtn.setTitle("Start", for: .normal) 99 | self.currentLocationLbl.text = "" 100 | self.locatingSubscription!.dispose() 101 | self.locatingSubscription = nil 102 | } 103 | } 104 | .addDisposableTo(disposeBag) 105 | } 106 | 107 | override func viewDidDisappear(_ animated: Bool) { 108 | disposeBag = nil 109 | locatedSubscription?.dispose() 110 | locatedSubscription = nil 111 | locatingSubscription?.dispose() 112 | locatingSubscription = nil 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /RxLocationManagerDemo/VisitMonitoringViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisitMonitoringViewController.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/15. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreLocation 11 | import RxLocationManager 12 | import RxSwift 13 | 14 | class VisitMonitoringViewController: UIViewController { 15 | 16 | @IBOutlet weak var coordValueLbl: UILabel! 17 | @IBOutlet weak var horizontalAccuracyLbl: UILabel! 18 | @IBOutlet weak var arriveDateLbl: UILabel! 19 | @IBOutlet weak var departureDateLbl: UILabel! 20 | @IBOutlet weak var toggleBtn: UIButton! 21 | 22 | private var disposeBag:DisposeBag! 23 | private var subscription: Disposable? 24 | override func viewWillAppear(_ animated: Bool) { 25 | disposeBag = DisposeBag() 26 | toggleBtn.rx.tap 27 | .subscribe( 28 | onNext:{ 29 | [unowned self] 30 | _ in 31 | if self.subscription != nil{ 32 | self.subscription!.dispose() 33 | self.toggleBtn.setTitle("Start", for: .normal) 34 | self.subscription = nil 35 | }else{ 36 | self.subscription = RxLocationManager.VisitMonitoring.visiting 37 | .subscribe(onNext:{ 38 | visit in 39 | self.coordValueLbl.text = "\(visit.coordinate.latitude),\(visit.coordinate.longitude)" 40 | self.horizontalAccuracyLbl.text = visit.horizontalAccuracy.description 41 | self.arriveDateLbl.text = visit.arrivalDate.description 42 | self.departureDateLbl.text = visit.departureDate.description 43 | }) 44 | self.toggleBtn.setTitle("Stop", for: .normal) 45 | } 46 | }) 47 | .addDisposableTo(disposeBag) 48 | } 49 | 50 | override func viewDidDisappear(_ animated: Bool) { 51 | disposeBag = nil 52 | subscription?.dispose() 53 | subscription = nil 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /RxLocationManagerTests/Fixtures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fixtures.swift 3 | // RxLocationManager 4 | // 5 | // Created by Hao Yu on 16/7/24. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | let dummyLocationManager = CLLocationManager() 13 | 14 | struct Locations{ 15 | static let London = CLLocation(latitude: 51.50, longitude: -0.13) 16 | static let Johnannesburg = CLLocation(latitude: -26.20, longitude: 28.05) 17 | static let Moscow = CLLocation(latitude: 55.75, longitude: 37.62) 18 | static let Mumbai = CLLocation(latitude: 19.02, longitude: 72.86) 19 | static let Tokyo = CLLocation(latitude: 35.70, longitude: 139.78) 20 | static let Sydney = CLLocation(latitude: -33.86, longitude: 151.21) 21 | } 22 | 23 | struct GeoRegions{ 24 | static let London = CLCircularRegion(center: Locations.London.coordinate, radius: 100, identifier: "London") 25 | static let Johnannesburg = CLCircularRegion(center: Locations.Johnannesburg.coordinate, radius: 100, identifier: "Johnannesburg") 26 | static let Moscow = CLCircularRegion(center: Locations.Moscow.coordinate, radius: 100, identifier: "Moscow") 27 | static let Mumbai = CLCircularRegion(center: Locations.Mumbai.coordinate, radius: 100, identifier: "Mumbai") 28 | static let Tokyo = CLCircularRegion(center: Locations.Tokyo.coordinate, radius: 100, identifier: "Tokyo") 29 | static let Sydney = CLCircularRegion(center: Locations.Sydney.coordinate, radius: 100, identifier: "Sydney") 30 | } 31 | 32 | #if os(iOS) 33 | struct BeaconRegions{ 34 | static let one = CLBeaconRegion(proximityUUID: UUID(uuidString: "436F7E14-D361-4D9E-8A0B-9C5B780788C0")!, identifier: "one") 35 | static let two = CLBeaconRegion(proximityUUID: UUID(uuidString: "A36C2C84-CFC8-4E2F-BEE8-9036A7CBD26D")!, identifier: "two") 36 | static let three = CLBeaconRegion(proximityUUID: UUID(uuidString: "6CE0D127-42AC-45B9-839C-0B6AD53EBE11")!, identifier: "three") 37 | } 38 | #endif 39 | 40 | #if os(iOS) 41 | class CLHeadingForTest: CLHeading{ 42 | override init(){super.init()} 43 | required init?(coder aDecoder: NSCoder) { 44 | super.init(coder: aDecoder) 45 | } 46 | } 47 | 48 | struct Headings{ 49 | static let north = CLHeadingForTest() 50 | static let south = CLHeadingForTest() 51 | static let east = CLHeadingForTest() 52 | static let west = CLHeadingForTest() 53 | } 54 | #endif 55 | 56 | #if os(iOS) 57 | struct Visits{ 58 | static let one = CLVisitForTest() 59 | static let two = CLVisitForTest() 60 | } 61 | class CLVisitForTest: CLVisit{ 62 | override init(){super.init()} 63 | 64 | required init?(coder aDecoder: NSCoder) { 65 | super.init(coder: aDecoder) 66 | } 67 | } 68 | #endif 69 | 70 | 71 | 72 | extension CLError.Code{ 73 | func toNSError() -> NSError{ 74 | return NSError(domain: kCLErrorDomain, code: rawValue, userInfo: nil) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /RxLocationManagerTests/HeadingUpdateServiceTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeadingUpdateServiceTest.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/25. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxSwift 11 | import CoreLocation 12 | import Nimble 13 | @testable 14 | import RxLocationManager 15 | #if os(iOS) 16 | class HeadingUpdateServiceTest: XCTestCase { 17 | var headingUpdateService: DefaultHeadingUpdateService! 18 | var bridge: LocationManagerStub! 19 | var disposeBag: DisposeBag! 20 | override func setUp() { 21 | headingUpdateService = DefaultHeadingUpdateService(bridgeClass: LocationManagerStub.self) 22 | bridge = headingUpdateService.locMgr as! LocationManagerStub 23 | disposeBag = DisposeBag() 24 | } 25 | 26 | override func tearDown() { 27 | disposeBag = nil 28 | } 29 | 30 | func testGetSetHeadingFilter() { 31 | _ = headingUpdateService.headingFilter(20.0) 32 | expect(self.headingUpdateService.headingFilter).to(equal(bridge.headingFilter)) 33 | expect(self.headingUpdateService.headingFilter).to(equal(20.0)) 34 | } 35 | func testGetSetHeadingOrientation() { 36 | _ = headingUpdateService.headingOrientation(CLDeviceOrientation.faceDown) 37 | expect(self.headingUpdateService.headingOrientation).to(equal(bridge.headingOrientation)) 38 | expect(self.headingUpdateService.headingOrientation).to(equal(CLDeviceOrientation.faceDown)) 39 | } 40 | func testGetSetDisplayHeadingCalibration() { 41 | _ = headingUpdateService.displayHeadingCalibration(false) 42 | expect(self.headingUpdateService.displayHeadingCalibration).to(equal(bridge.displayHeadingCalibration)) 43 | expect(self.headingUpdateService.displayHeadingCalibration).to(beFalse()) 44 | } 45 | func testGetSetTrueHeading() { 46 | _ = headingUpdateService.startTrueHeading((100, kCLLocationAccuracyKilometer)) 47 | expect(self.bridge.currentDistanceFilter).to(equal(100)) 48 | expect(self.bridge.currentDesiredAccuracy).to(equal(kCLLocationAccuracyKilometer)) 49 | expect(self.bridge.updatingLocation).to(beTrue()) 50 | headingUpdateService.stopTrueHeading() 51 | expect(self.bridge.updatingLocation).to(beFalse()) 52 | } 53 | func testHeadingObservable() { 54 | let xcTestExpectation = self.expectation(description: "Get one heading update") 55 | headingUpdateService.heading 56 | .subscribe(onNext: { 57 | heading in 58 | expect(heading == Headings.north).to(beTrue()) 59 | xcTestExpectation.fulfill() 60 | }) 61 | .addDisposableTo(disposeBag) 62 | self.bridge.didUpdateHeading!(dummyLocationManager, Headings.north) 63 | self.waitForExpectations(timeout: 50, handler: nil) 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /RxLocationManagerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /RxLocationManagerTests/LocationManagerStub.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationManagerStub.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/24. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | @testable 12 | import RxLocationManager 13 | 14 | class LocationManagerStub: CLLocationManagerBridge{ 15 | var whenInUseAuthorizationRequested = false 16 | var alwaysAuthorizationRequested = false 17 | 18 | var currentLocation:CLLocation? 19 | var currentDistanceFilter: CLLocationDistance = 0.0 20 | var currentDesiredAccuracy: CLLocationAccuracy = 0.0 21 | var currentPausesLocationUpdatesAutomatically = false 22 | var currentAllowsBackgroundLocationUpdates = false 23 | var currentActivityType = CLActivityType.other 24 | 25 | var currentlyDeferedSetting:(CLLocationDistance, TimeInterval)? 26 | 27 | var currentHeadingFilter = 0.0 28 | var currentHeadingOrientation = CLDeviceOrientation.portrait 29 | 30 | var currentMonitoredRegions = Set() 31 | var currentRegionStateRequests = Set() 32 | 33 | #if os(iOS) 34 | var currangRangedBeaconRegions = Set() 35 | #endif 36 | 37 | var updatingLocation = false 38 | var locationRequested = false 39 | var updatingHeading = false 40 | var monitoringSignificantLocationChange = false 41 | 42 | //instance methods on CLLocationManager instance 43 | #if os(iOS) || os(watchOS) || os(tvOS) 44 | override func requestWhenInUseAuthorization(){ 45 | whenInUseAuthorizationRequested = true 46 | } 47 | #endif 48 | #if os(iOS) || os(watchOS) 49 | override func requestAlwaysAuthorization(){ 50 | alwaysAuthorizationRequested = true 51 | } 52 | #endif 53 | 54 | #if os(iOS) || os(watchOS) || os(tvOS) 55 | override var location: CLLocation? { 56 | get{ 57 | return currentLocation 58 | } 59 | } 60 | #endif 61 | 62 | #if os(iOS) || os(OSX) 63 | override func startUpdatingLocation(){ 64 | updatingLocation = true 65 | } 66 | #endif 67 | 68 | override func stopUpdatingLocation(){ 69 | updatingLocation = false 70 | } 71 | 72 | #if os(iOS) || os(watchOS) || os(tvOS) 73 | @available(iOS 9.0, *) 74 | override func requestLocation(){ 75 | locationRequested = true 76 | } 77 | #endif 78 | 79 | override var distanceFilter: CLLocationDistance { 80 | get{ 81 | return currentDistanceFilter 82 | } 83 | set{ 84 | currentDistanceFilter = newValue 85 | } 86 | } 87 | override var desiredAccuracy: CLLocationAccuracy { 88 | get{ 89 | return currentDesiredAccuracy 90 | } 91 | set{ 92 | currentDesiredAccuracy = newValue 93 | } 94 | } 95 | #if os(iOS) 96 | override var pausesLocationUpdatesAutomatically: Bool { 97 | get{ 98 | return currentPausesLocationUpdatesAutomatically 99 | } 100 | set{ 101 | currentPausesLocationUpdatesAutomatically = newValue 102 | } 103 | } 104 | @available(iOS 9.0, *) 105 | override var allowsBackgroundLocationUpdates: Bool { 106 | get{ 107 | return currentAllowsBackgroundLocationUpdates 108 | } 109 | set{ 110 | return currentAllowsBackgroundLocationUpdates = newValue 111 | } 112 | } 113 | override func allowDeferredLocationUpdates(untilTraveled distance: CLLocationDistance, timeout: TimeInterval){ 114 | currentlyDeferedSetting = (distance, timeout) 115 | } 116 | override func disallowDeferredLocationUpdates(){ 117 | currentlyDeferedSetting = nil 118 | } 119 | override var activityType: CLActivityType { 120 | get{ 121 | return currentActivityType 122 | } 123 | set{ 124 | currentActivityType = newValue 125 | } 126 | } 127 | #endif 128 | 129 | #if os(iOS) || os(OSX) 130 | override func startMonitoringSignificantLocationChanges(){ 131 | monitoringSignificantLocationChange = true 132 | } 133 | override func stopMonitoringSignificantLocationChanges(){ 134 | monitoringSignificantLocationChange = false 135 | } 136 | #endif 137 | 138 | #if os(iOS) 139 | override func startUpdatingHeading(){ 140 | updatingHeading = true 141 | } 142 | override func stopUpdatingHeading(){ 143 | updatingHeading = false 144 | } 145 | override func dismissHeadingCalibrationDisplay(){ 146 | 147 | } 148 | override var headingFilter: CLLocationDegrees { 149 | get{ 150 | return currentHeadingFilter 151 | } 152 | set{ 153 | currentHeadingFilter = newValue 154 | } 155 | } 156 | override var headingOrientation: CLDeviceOrientation { 157 | get{ 158 | return currentHeadingOrientation 159 | } 160 | set{ 161 | currentHeadingOrientation = newValue 162 | } 163 | } 164 | #endif 165 | 166 | #if os(iOS) || os(OSX) 167 | override func startMonitoring(for region: CLRegion){ 168 | currentMonitoredRegions.insert(region) 169 | } 170 | override func stopMonitoring(for region: CLRegion){ 171 | currentMonitoredRegions.remove(region) 172 | } 173 | override var monitoredRegions: Set { 174 | get{ 175 | return currentMonitoredRegions 176 | } 177 | } 178 | override var maximumRegionMonitoringDistance: CLLocationDistance { 179 | get{ 180 | return 200 181 | } 182 | } 183 | override func requestState(for region: CLRegion){ 184 | currentRegionStateRequests.insert(region) 185 | } 186 | #endif 187 | 188 | #if os(iOS) 189 | override var rangedRegions: Set { 190 | get{ 191 | return currangRangedBeaconRegions 192 | } 193 | } 194 | override func startRangingBeacons(in region: CLBeaconRegion){ 195 | currangRangedBeaconRegions.insert(region) 196 | } 197 | override func stopRangingBeacons(in region: CLBeaconRegion){ 198 | currangRangedBeaconRegions.remove(region) 199 | } 200 | #endif 201 | 202 | #if os(iOS) 203 | override func startMonitoringVisits(){ 204 | 205 | } 206 | override func stopMonitoringVisits(){ 207 | 208 | } 209 | #endif 210 | 211 | required init(){ 212 | super.init() 213 | } 214 | 215 | } 216 | 217 | -------------------------------------------------------------------------------- /RxLocationManagerTests/MonitoringVisitsServiceTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonitoringVisitsServiceTest.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/27. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxSwift 11 | import CoreLocation 12 | import Nimble 13 | @testable 14 | import RxLocationManager 15 | #if os(iOS) 16 | class MonitoringVisitsServiceTest: XCTestCase { 17 | var monitoringVisitsService: DefaultMonitoringVisitsService! 18 | var bridge: LocationManagerStub! 19 | var disposeBag: DisposeBag! 20 | override func setUp() { 21 | monitoringVisitsService = DefaultMonitoringVisitsService(bridgeClass: LocationManagerStub.self) 22 | bridge = monitoringVisitsService.locMgr as! LocationManagerStub 23 | disposeBag = DisposeBag() 24 | } 25 | 26 | override func tearDown() { 27 | disposeBag = nil 28 | } 29 | 30 | func testVisitsObservable() { 31 | let xcTestExpectation = self.expectation(description: "Get one visited place") 32 | monitoringVisitsService.visiting 33 | .subscribe(onNext: { 34 | visit in 35 | expect(visit == Visits.one).to(beTrue()) 36 | xcTestExpectation.fulfill() 37 | }) 38 | .addDisposableTo(disposeBag) 39 | self.bridge.didVisit!(dummyLocationManager, Visits.one) 40 | self.waitForExpectations(timeout: 50, handler: nil) 41 | 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /RxLocationManagerTests/RegionMonitoringServiceTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegionMonitoringServiceTest.swift 3 | // RxLocationManager 4 | // 5 | // Created by HaoYu on 16/7/26. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxSwift 11 | import CoreLocation 12 | import Nimble 13 | @testable 14 | import RxLocationManager 15 | #if os(iOS) || os(OSX) 16 | class RegionMonitoringServiceTest: XCTestCase { 17 | var regionMonitoringService: DefaultRegionMonitoringService! 18 | var bridge: LocationManagerStub! 19 | var disposeBag: DisposeBag! 20 | override func setUp() { 21 | regionMonitoringService = DefaultRegionMonitoringService(bridgeClass: LocationManagerStub.self) 22 | bridge = regionMonitoringService.locMgr as! LocationManagerStub 23 | disposeBag = DisposeBag() 24 | } 25 | 26 | override func tearDown() { 27 | disposeBag = nil 28 | } 29 | 30 | func testStartMonitoringForRegions() { 31 | _ = regionMonitoringService.startMonitoringForRegions([GeoRegions.London, GeoRegions.Johnannesburg]) 32 | expect(self.bridge.currentMonitoredRegions).to(equal([GeoRegions.London, GeoRegions.Johnannesburg])) 33 | } 34 | 35 | func testStopMonitoringForRegions() { 36 | _ = regionMonitoringService.startMonitoringForRegions([GeoRegions.London, GeoRegions.Johnannesburg]) 37 | _ = regionMonitoringService.stopMonitoringForRegions([GeoRegions.London]) 38 | expect(self.bridge.currentMonitoredRegions).to(equal([GeoRegions.Johnannesburg])) 39 | } 40 | 41 | func testStopAllMonitoringForRegions() { 42 | _ = regionMonitoringService.startMonitoringForRegions([GeoRegions.London, GeoRegions.Johnannesburg]) 43 | _ = regionMonitoringService.stopMonitoringForAllRegions() 44 | expect(self.bridge.currentMonitoredRegions.count).to(equal(0)) 45 | } 46 | 47 | func testRequestStateForRegion() { 48 | _ = regionMonitoringService.requestRegionsState([GeoRegions.London, GeoRegions.Johnannesburg]) 49 | expect(self.bridge.currentRegionStateRequests).to(equal([GeoRegions.London, GeoRegions.Johnannesburg])) 50 | } 51 | 52 | #if os(iOS) 53 | func testStartRangingBeaconsInRegion(){ 54 | _ = regionMonitoringService.startRangingBeaconsInRegion(BeaconRegions.one) 55 | _ = regionMonitoringService.startRangingBeaconsInRegion(BeaconRegions.two) 56 | expect(self.bridge.currangRangedBeaconRegions).to(equal([BeaconRegions.one, BeaconRegions.two])) 57 | expect(self.bridge.currangRangedBeaconRegions).to(equal(regionMonitoringService.rangedRegions)) 58 | } 59 | 60 | func testStopRangingBeaconsInRegion(){ 61 | _ = regionMonitoringService.startRangingBeaconsInRegion(BeaconRegions.one) 62 | _ = regionMonitoringService.startRangingBeaconsInRegion(BeaconRegions.two) 63 | _ = regionMonitoringService.startRangingBeaconsInRegion(BeaconRegions.three) 64 | _ = regionMonitoringService.stopRangingBeaconsInRegion(BeaconRegions.three) 65 | expect(self.bridge.currangRangedBeaconRegions).to(equal([BeaconRegions.one, BeaconRegions.two])) 66 | } 67 | #endif 68 | 69 | func testGetMaximumRegionMonitoringDistance(){ 70 | expect(self.regionMonitoringService.maximumRegionMonitoringDistance).to(equal(200)) 71 | } 72 | 73 | func testMonitoredRegionsObservable(){ 74 | self.bridge.currentMonitoredRegions.insert(GeoRegions.London) 75 | let xcTestExpectation = self.expectation(description: "Get one monitored region") 76 | var n = 1 77 | regionMonitoringService.monitoredRegions 78 | .subscribe(onNext: { 79 | regions in 80 | if n == 1{ 81 | expect(regions).to(equal([GeoRegions.London])) 82 | n += 1 83 | }else{ 84 | expect(regions).to(equal([GeoRegions.London, GeoRegions.Johnannesburg])) 85 | xcTestExpectation.fulfill() 86 | } 87 | }) 88 | .addDisposableTo(disposeBag) 89 | 90 | self.bridge.currentMonitoredRegions.insert(GeoRegions.Johnannesburg) 91 | self.bridge.didStartMonitoringForRegion!(dummyLocationManager, GeoRegions.Johnannesburg) 92 | self.waitForExpectations(timeout: 50, handler: nil) 93 | } 94 | 95 | 96 | func testRegionEnteringEventObservable(){ 97 | let xcTestExpectation = self.expectation(description: "Get one monitored region enter event") 98 | regionMonitoringService.entering 99 | .subscribe(onNext: { 100 | region in 101 | expect(region).to(equal(GeoRegions.London)) 102 | xcTestExpectation.fulfill() 103 | }) 104 | .addDisposableTo(disposeBag) 105 | self.bridge.didEnterRegion!(dummyLocationManager, GeoRegions.London) 106 | self.waitForExpectations(timeout: 50, handler: nil) 107 | } 108 | 109 | func testRegionExitingEventObservable(){ 110 | let xcTestExpectation = self.expectation(description: "Get one monitored region exit event") 111 | regionMonitoringService.exiting 112 | .subscribe(onNext: { 113 | region in 114 | expect(region).to(equal(GeoRegions.London)) 115 | xcTestExpectation.fulfill() 116 | }) 117 | .addDisposableTo(disposeBag) 118 | self.bridge.didExitRegion!(dummyLocationManager, GeoRegions.London) 119 | self.waitForExpectations(timeout: 50, handler: nil) 120 | } 121 | 122 | func testDeterminedRegionStateObservable(){ 123 | let xcTestExpectation = self.expectation(description: "Determined state for one monitored region") 124 | regionMonitoringService.determinedRegionState 125 | .subscribe(onNext: { 126 | region, state in 127 | expect(region).to(equal(GeoRegions.London)) 128 | expect(state).to(equal(CLRegionState.inside)) 129 | xcTestExpectation.fulfill() 130 | }) 131 | .addDisposableTo(disposeBag) 132 | self.bridge.didDetermineState!(dummyLocationManager, CLRegionState.inside,GeoRegions.London) 133 | self.waitForExpectations(timeout: 50, handler: nil) 134 | } 135 | 136 | func testErrorObservableWithMonitoringError(){ 137 | let xcTestExpectation = self.expectation(description: "Get error during monitoring region") 138 | regionMonitoringService.error 139 | .subscribe(onNext: { 140 | region, error in 141 | expect(region!).to(equal(GeoRegions.London)) 142 | expect(error).to(equal(CLError.regionMonitoringFailure.toNSError())) 143 | xcTestExpectation.fulfill() 144 | }) 145 | .addDisposableTo(disposeBag) 146 | self.bridge.monitoringDidFailForRegion!(dummyLocationManager, GeoRegions.London, CLError.regionMonitoringFailure.toNSError()) 147 | self.waitForExpectations(timeout: 50, handler: nil) 148 | } 149 | 150 | #if os(iOS) 151 | func testErrorObservableWithRangingError(){ 152 | let xcTestExpectation = self.expectation(description: "Get error during ranging beacons") 153 | regionMonitoringService.error.subscribe(onNext: { 154 | region, error in 155 | expect(region!).to(equal(BeaconRegions.one)) 156 | expect(error).to(equal(CLError.Code.rangingFailure.toNSError())) 157 | xcTestExpectation.fulfill() 158 | }) 159 | .addDisposableTo(disposeBag) 160 | self.bridge.rangingBeaconsDidFailForRegion!(dummyLocationManager, BeaconRegions.one, CLError.Code.rangingFailure.toNSError()) 161 | self.waitForExpectations(timeout: 50, handler: nil) 162 | } 163 | 164 | func testRangingObservable(){ 165 | let xcTestExpectation = self.expectation(description: "Get ranged beacons") 166 | regionMonitoringService.ranging 167 | .subscribe(onNext: { 168 | beacons, beaconRegion in 169 | expect(beacons.count).to(equal(0)) 170 | expect(beaconRegion).to(equal(BeaconRegions.one)) 171 | xcTestExpectation.fulfill() 172 | }) 173 | .addDisposableTo(disposeBag) 174 | self.bridge.didRangeBeaconsInRegion!(dummyLocationManager, [], BeaconRegions.one) 175 | self.waitForExpectations(timeout: 50, handler: nil) 176 | } 177 | #endif 178 | } 179 | #endif 180 | -------------------------------------------------------------------------------- /RxLocationManagerTests/SignificantLocationUpdateServiceTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignificantLocationServiceTest.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/25. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | import XCTest 9 | import Nimble 10 | import CoreLocation 11 | import RxSwift 12 | @testable 13 | import RxLocationManager 14 | #if os(iOS) || os(OSX) 15 | class SignificantLocationUpdateServiceTest: XCTestCase { 16 | var significantLocationUpdateService:DefaultSignificantLocationUpdateService! 17 | var bridge:LocationManagerStub! 18 | var disposeBag: DisposeBag! 19 | override func setUp() { 20 | significantLocationUpdateService = DefaultSignificantLocationUpdateService(bridgeClass:LocationManagerStub.self) 21 | bridge = significantLocationUpdateService.locMgr as! LocationManagerStub 22 | disposeBag = DisposeBag() 23 | } 24 | 25 | override func tearDown() { 26 | disposeBag = nil 27 | } 28 | 29 | func testLocatingObservableWithoutError() { 30 | let xcTestExpectation = self.expectation(description: "GotSeriesOfLocations") 31 | var n = 1 32 | significantLocationUpdateService.locating 33 | .subscribe{ 34 | event in 35 | switch event{ 36 | case .next(let location): 37 | switch n{ 38 | case 1: 39 | expect(location.last!).to(equal(Locations.London)) 40 | n += 1 41 | case 2: 42 | expect(location.last!).to(equal(Locations.Johnannesburg)) 43 | n += 1 44 | case 3: 45 | expect(location.last!).to(equal(Locations.Moscow)) 46 | xcTestExpectation.fulfill() 47 | default: 48 | expect(true).to(beFalse(), description: "You should not be here") 49 | } 50 | case .completed: 51 | expect(true).to(beFalse(), description: "Completed should not get called when observing location updating") 52 | case .error: 53 | expect(true).to(beFalse(), description: "Error should not get called when location is reported") 54 | } 55 | } 56 | .addDisposableTo(disposeBag) 57 | expect(self.bridge.monitoringSignificantLocationChange).to(beTrue()) 58 | bridge.didUpdateLocations!(dummyLocationManager, [Locations.London]) 59 | bridge.didUpdateLocations!(dummyLocationManager, [Locations.Johnannesburg]) 60 | bridge.didUpdateLocations!(dummyLocationManager, [Locations.Moscow]) 61 | self.waitForExpectations(timeout: 100, handler:nil) 62 | } 63 | 64 | func testLocatingObservableWithError() { 65 | let xcTextExpectation = self.expectation(description: "GotSeriesOfLocationsAndError") 66 | var n = 1 67 | significantLocationUpdateService.locating 68 | .subscribe{ 69 | event in 70 | switch event{ 71 | case .next(let location): 72 | switch n{ 73 | case 1: 74 | expect(location.last!).to(equal(Locations.London)) 75 | n += 1 76 | case 2: 77 | expect(location.last!).to(equal(Locations.Johnannesburg)) 78 | n += 1 79 | case 3: 80 | expect(true).to(beFalse(), description: "You should not be here") 81 | default: 82 | expect(true).to(beFalse(), description: "You should not be here") 83 | } 84 | case .completed: 85 | expect(true).to(beFalse(), description: "Completed should not get called when observing location updating") 86 | case .error(let error as NSError): 87 | expect(error.domain == CLError.network.toNSError().domain).to(beTrue()) 88 | expect(error.code == CLError.network.toNSError().code).to(beTrue()) 89 | xcTextExpectation.fulfill() 90 | default: 91 | expect(true).to(beFalse(), description: "You should not be here") 92 | } 93 | } 94 | .addDisposableTo(disposeBag) 95 | bridge.didUpdateLocations!(dummyLocationManager, [Locations.London]) 96 | bridge.didUpdateLocations!(dummyLocationManager, [Locations.Johnannesburg]) 97 | bridge.didFailWithError!(dummyLocationManager, CLError.network.toNSError()) 98 | bridge.didUpdateLocations!(dummyLocationManager, [Locations.Moscow]) 99 | self.waitForExpectations(timeout: 100, handler:nil) 100 | } 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /RxLocationManagerTests/StandardLocationServiceTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardLocationServiceTest.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/24. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Nimble 11 | import CoreLocation 12 | import RxSwift 13 | @testable 14 | import RxLocationManager 15 | 16 | class StandardLocationServiceTest: XCTestCase{ 17 | var standardLocationService:DefaultStandardLocationService! 18 | var disposeBag: DisposeBag! 19 | var bridgeForLocation:LocationManagerStub! 20 | var bridgeForLocating:LocationManagerStub! 21 | override func setUp() { 22 | standardLocationService = DefaultStandardLocationService(bridgeClass:LocationManagerStub.self) 23 | #if os(iOS) || os(watchOS) || os(tvOS) 24 | bridgeForLocation = standardLocationService.locMgrForLocation as! LocationManagerStub 25 | #endif 26 | #if os(iOS) || os(OSX) 27 | bridgeForLocating = standardLocationService.locMgrForLocating as! LocationManagerStub 28 | #endif 29 | disposeBag = DisposeBag() 30 | } 31 | 32 | override func tearDown() { 33 | disposeBag = nil 34 | } 35 | 36 | func testGetSetDistanceFilter(){ 37 | _ = standardLocationService.distanceFilter(10.0) 38 | #if os(iOS) || os(watchOS) || os(tvOS) 39 | expect(self.standardLocationService.locMgrForLocation.distanceFilter).to(equal(10.0)) 40 | #endif 41 | 42 | #if os(iOS) || os(OSX) 43 | expect(self.standardLocationService.locMgrForLocating.distanceFilter).to(equal(10.0)) 44 | #endif 45 | } 46 | 47 | func testGetSetDesiredAccuracy(){ 48 | _ = standardLocationService.desiredAccuracy(100.0) 49 | expect(self.standardLocationService.desiredAccuracy).to(equal(100.0)) 50 | } 51 | #if os(iOS) 52 | func testGetSetPausesLocationUpdatesAutomatically(){ 53 | _ = standardLocationService.pausesLocationUpdatesAutomatically(true) 54 | expect(self.standardLocationService.locMgrForLocating.pausesLocationUpdatesAutomatically).to(equal(true)) 55 | 56 | } 57 | 58 | func testEnableDeferredLocationUpdates(){ 59 | _ = standardLocationService.allowDeferredLocationUpdates(untilTraveled: 100, timeout: 60) 60 | expect((self.standardLocationService.locMgrForLocating as! LocationManagerStub).currentlyDeferedSetting! == (100,60)).to(beTrue()) 61 | } 62 | 63 | func testDisableDeferredLocationUpdates(){ 64 | _ = standardLocationService.allowDeferredLocationUpdates(untilTraveled: 100, timeout: 60) 65 | _ = standardLocationService.disallowDeferredLocationUpdates() 66 | expect((self.standardLocationService.locMgrForLocating as! LocationManagerStub).currentlyDeferedSetting == nil).to(beTrue()) 67 | } 68 | 69 | func testGetSetAllowsBgLocationUpdates(){ 70 | _ = standardLocationService.allowsBackgroundLocationUpdates(true) 71 | expect(self.standardLocationService.locMgrForLocating.allowsBackgroundLocationUpdates).to(equal(true)) 72 | 73 | } 74 | 75 | func testGetSetActivityType(){ 76 | _ = standardLocationService.activityType(CLActivityType.automotiveNavigation) 77 | expect(self.standardLocationService.locMgrForLocating.activityType).to(equal(CLActivityType.automotiveNavigation)) 78 | } 79 | #endif 80 | 81 | #if os(iOS) || os(watchOS) || os(tvOS) 82 | func testCurrentLocationObservable(){ 83 | let xcTextExpectation1 = self.expectation(description: "GotLocationAndComplete") 84 | standardLocationService.located 85 | .subscribe{ 86 | event in 87 | switch event{ 88 | case .next(let location): 89 | expect(location).to(equal(Locations.London)) 90 | case .completed: 91 | xcTextExpectation1.fulfill() 92 | case .error: 93 | expect(true).to(beFalse(), description: "Error should not get called when location is reported") 94 | } 95 | } 96 | .addDisposableTo(disposeBag) 97 | bridgeForLocation.didUpdateLocations!(dummyLocationManager, [Locations.London]) 98 | self.waitForExpectations(timeout: 5, handler:nil) 99 | 100 | let xcTextExpectation2 = self.expectation(description: "GotError") 101 | standardLocationService.located 102 | .subscribe{ 103 | event in 104 | switch event{ 105 | case .next: 106 | expect(true).to(beFalse(), description: "Next should not get called when error is reported") 107 | case .completed: 108 | expect(true).to(beFalse(), description: "Completed should not get called when error is reported") 109 | case .error(let error as NSError): 110 | expect(error.domain == CLError.locationUnknown.toNSError().domain).to(beTrue()) 111 | expect(error.code == CLError.locationUnknown.toNSError().code).to(beTrue()) 112 | xcTextExpectation2.fulfill() 113 | default: 114 | expect(true).to(beFalse(), description: "You should not be here") 115 | } 116 | } 117 | .addDisposableTo(disposeBag) 118 | bridgeForLocation.didFailWithError!(dummyLocationManager, CLError.locationUnknown.toNSError()) 119 | self.waitForExpectations(timeout: 5, handler:nil) 120 | } 121 | #endif 122 | #if os(iOS) || os(OSX) 123 | func testLocatingObservable(){ 124 | let xcTextExpectation = self.expectation(description: "GotSeriesOfLocations") 125 | var n = 1 126 | standardLocationService.locating 127 | .subscribe{ 128 | event in 129 | switch event{ 130 | case .next(let location): 131 | switch n{ 132 | case 1: 133 | expect(location.last!).to(equal(Locations.London)) 134 | n += 1 135 | case 2: 136 | expect(location.last!).to(equal(Locations.Johnannesburg)) 137 | n += 1 138 | case 3: 139 | expect(location.last!).to(equal(Locations.Moscow)) 140 | xcTextExpectation.fulfill() 141 | default: 142 | expect(true).to(beFalse(), description: "You should not be here") 143 | } 144 | case .completed: 145 | expect(true).to(beFalse(), description: "Completed should not get called when observing location updating") 146 | case .error: 147 | expect(true).to(beFalse(), description: "Error should not get called when location is reported") 148 | } 149 | } 150 | .addDisposableTo(disposeBag) 151 | expect(self.bridgeForLocating.updatingLocation).to(beTrue()) 152 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.London]) 153 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.Johnannesburg]) 154 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.Moscow]) 155 | self.waitForExpectations(timeout: 100, handler:nil) 156 | } 157 | 158 | func testLocatingObservableWithIgnorableError(){ 159 | let xcTextExpectation = self.expectation(description: "GotSeriesOfLocationsAndIgnoreLocationUnknownError") 160 | var n = 1 161 | standardLocationService.locating 162 | .subscribe{ 163 | event in 164 | switch event{ 165 | case .next(let location): 166 | switch n{ 167 | case 1: 168 | expect(location.last!).to(equal(Locations.London)) 169 | n += 1 170 | case 2: 171 | expect(location.last!).to(equal(Locations.Johnannesburg)) 172 | n += 1 173 | case 3: 174 | expect(location.last!).to(equal(Locations.Moscow)) 175 | xcTextExpectation.fulfill() 176 | default: 177 | expect(true).to(beFalse(), description: "You should not be here") 178 | } 179 | case .completed: 180 | expect(true).to(beFalse(), description: "Completed should not get called when observing location updating") 181 | case .error: 182 | expect(true).to(beFalse(), description: "Error should not get called when location is reported") 183 | } 184 | } 185 | .addDisposableTo(disposeBag) 186 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.London]) 187 | bridgeForLocating.didFailWithError!(dummyLocationManager, CLError.locationUnknown.toNSError()) 188 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.Johnannesburg]) 189 | bridgeForLocating.didFailWithError!(dummyLocationManager, CLError.locationUnknown.toNSError()) 190 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.Moscow]) 191 | bridgeForLocating.didFailWithError!(dummyLocationManager, CLError.locationUnknown.toNSError()) 192 | self.waitForExpectations(timeout: 5, handler:nil) 193 | } 194 | 195 | func testLocatingObservableWithError(){ 196 | let xcTextExpectation = self.expectation(description: "GotSeriesOfLocationsAndNonIgnorableError") 197 | var n = 1 198 | standardLocationService.locating 199 | .subscribe{ 200 | event in 201 | switch event{ 202 | case .next(let location): 203 | switch n{ 204 | case 1: 205 | expect(location.last!).to(equal(Locations.London)) 206 | n += 1 207 | case 2: 208 | expect(location.last!).to(equal(Locations.Johnannesburg)) 209 | n += 1 210 | case 3: 211 | expect(true).to(beFalse(), description: "You should not be here") 212 | default: 213 | expect(true).to(beFalse(), description: "You should not be here") 214 | } 215 | case .completed: 216 | expect(true).to(beFalse(), description: "Completed should not get called when observing location updating") 217 | case .error: 218 | xcTextExpectation.fulfill() 219 | } 220 | } 221 | .addDisposableTo(disposeBag) 222 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.London]) 223 | bridgeForLocating.didFailWithError!(dummyLocationManager, CLError.locationUnknown.toNSError()) 224 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.Johnannesburg]) 225 | bridgeForLocating.didFailWithError!(dummyLocationManager, CLError.denied.toNSError()) 226 | bridgeForLocating.didUpdateLocations!(dummyLocationManager, [Locations.Moscow]) 227 | self.waitForExpectations(timeout: 5, handler:nil) 228 | } 229 | #endif 230 | 231 | #if os(iOS) 232 | func testPausedObservable(){ 233 | let xcTextExpectation = self.expectation(description: "ObservableOfIsPaused") 234 | var n = 1 235 | standardLocationService.isPaused 236 | .subscribe{ 237 | event in 238 | switch event{ 239 | case .next(let isPaused): 240 | switch n{ 241 | case 1: 242 | expect(isPaused).to(beTrue()) 243 | n += 1 244 | case 2: 245 | expect(isPaused).to(beFalse()) 246 | xcTextExpectation.fulfill() 247 | n += 1 248 | default: 249 | expect(true).to(beFalse(), description: "You should not be here") 250 | } 251 | case .completed: 252 | expect(true).to(beFalse(), description: "Completed should not get called for this observable") 253 | case .error: 254 | expect(true).to(beFalse(), description: "Error should not get called for this observable") 255 | } 256 | } 257 | .addDisposableTo(disposeBag) 258 | bridgeForLocating.didPausedUpdate!(dummyLocationManager) 259 | bridgeForLocating.didResumeUpdate!(dummyLocationManager) 260 | self.waitForExpectations(timeout: 5, handler:nil) 261 | } 262 | 263 | func testDeferredUpdateErrorObservable(){ 264 | let xcTextExpectation = self.expectation(description: "ObservableOfIsPaused") 265 | standardLocationService.deferredUpdateFinished 266 | .subscribe{ 267 | event in 268 | switch event{ 269 | case .next(let error): 270 | expect(error!.code == CLError.deferredAccuracyTooLow.toNSError().code).to(beTrue()) 271 | xcTextExpectation.fulfill() 272 | case .completed: 273 | expect(true).to(beFalse(), description: "Completed should not get called for this observable") 274 | case .error: 275 | expect(true).to(beFalse(), description: "Error should not get called for this observable") 276 | } 277 | } 278 | .addDisposableTo(disposeBag) 279 | bridgeForLocating.didFinishDeferredUpdatesWithError!(dummyLocationManager, CLError.deferredAccuracyTooLow.toNSError()) 280 | self.waitForExpectations(timeout: 5, handler:nil) 281 | } 282 | #endif 283 | } 284 | -------------------------------------------------------------------------------- /Sources/Bridge.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bridge.swift 3 | // RxLocationManager 4 | // 5 | // Created by Hao Yu on 16/7/6. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | 12 | class CLLocationManagerBridge: CLLocationManager, CLLocationManagerDelegate{ 13 | var didFailWithError: ((CLLocationManager, NSError) -> Void)? 14 | var didChangeAuthorizationStatus: ((CLLocationManager, CLAuthorizationStatus)->Void)? 15 | var didUpdateLocations: ((CLLocationManager, [CLLocation]) -> Void)? 16 | 17 | #if os(iOS) || os(OSX) 18 | var didFinishDeferredUpdatesWithError: ((CLLocationManager, NSError?) -> Void)? 19 | var didEnterRegion: ((CLLocationManager, CLRegion) -> Void)? 20 | var didExitRegion: ((CLLocationManager, CLRegion) -> Void)? 21 | var monitoringDidFailForRegion: ((CLLocationManager, CLRegion?, NSError) -> Void)? 22 | var didDetermineState:((CLLocationManager, CLRegionState, CLRegion) -> Void)? 23 | var didStartMonitoringForRegion:((CLLocationManager, CLRegion) -> Void)? 24 | #endif 25 | 26 | #if os(iOS) 27 | var didPausedUpdate:((CLLocationManager) -> Void)? 28 | var didResumeUpdate:((CLLocationManager) -> Void)? 29 | var displayHeadingCalibration:Bool = false 30 | var didUpdateHeading: ((CLLocationManager, CLHeading) -> Void)? 31 | var didRangeBeaconsInRegion:((CLLocationManager, [CLBeacon], CLBeaconRegion) -> Void)? 32 | var rangingBeaconsDidFailForRegion:((CLLocationManager, CLBeaconRegion, NSError) -> Void)? 33 | var didVisit:((CLLocationManager, CLVisit) -> Void)? 34 | #endif 35 | 36 | required override init() { 37 | super.init() 38 | self.delegate = self 39 | } 40 | 41 | func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { 42 | didFailWithError?(manager, error as NSError) 43 | } 44 | 45 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 46 | didChangeAuthorizationStatus?(manager, status) 47 | } 48 | 49 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 50 | didUpdateLocations?(manager, locations) 51 | } 52 | } 53 | 54 | #if os(iOS) || os(OSX) 55 | extension CLLocationManagerBridge{ 56 | 57 | func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) { 58 | didDetermineState?(manager, state, region) 59 | } 60 | 61 | func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) { 62 | didEnterRegion?(manager, region) 63 | } 64 | 65 | func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) { 66 | didExitRegion?(manager, region) 67 | } 68 | 69 | func locationManager(manager: CLLocationManager, monitoringDidFailForRegion region: CLRegion?, withError error: NSError) { 70 | monitoringDidFailForRegion?(manager, region, error) 71 | } 72 | 73 | func locationManager(manager: CLLocationManager, didStartMonitoringForRegion region: CLRegion) { 74 | didStartMonitoringForRegion?(manager, region) 75 | } 76 | } 77 | #endif 78 | 79 | #if os(iOS) 80 | extension CLLocationManagerBridge{ 81 | 82 | func locationManager(manager: CLLocationManager, didFinishDeferredUpdatesWithError error: NSError?) { 83 | didFinishDeferredUpdatesWithError?(manager, error) 84 | } 85 | 86 | func locationManagerDidPauseLocationUpdates(manager: CLLocationManager) { 87 | didPausedUpdate?(manager) 88 | } 89 | 90 | func locationManagerDidResumeLocationUpdates(manager: CLLocationManager) { 91 | didResumeUpdate?(manager) 92 | } 93 | 94 | func locationManagerShouldDisplayHeadingCalibration(manager: CLLocationManager) -> Bool { 95 | return displayHeadingCalibration 96 | } 97 | 98 | func locationManager(manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { 99 | didUpdateHeading?(manager, newHeading) 100 | } 101 | 102 | func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion) { 103 | didRangeBeaconsInRegion?(manager, beacons, region) 104 | } 105 | 106 | func locationManager(manager: CLLocationManager, rangingBeaconsDidFailForRegion region: CLBeaconRegion, withError error: NSError){ 107 | rangingBeaconsDidFailForRegion?(manager, region, error) 108 | } 109 | 110 | func locationManager(manager: CLLocationManager, didVisit visit: CLVisit) { 111 | didVisit?(manager, visit) 112 | } 113 | } 114 | #endif 115 | -------------------------------------------------------------------------------- /Sources/HeadingUpdateService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeadingUpdateService.swift 3 | // RxLocationManager 4 | // 5 | // Created by Hao Yu on 16/7/6. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | import Foundation 12 | import CoreLocation 13 | import RxSwift 14 | 15 | //MARK: HeadingUpdateServiceConfigurable 16 | public protocol HeadingUpdateServiceConfigurable{ 17 | /** 18 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instp/CLLocationManager/headingFilter) 19 | 20 | - parameter degrees: to filter 21 | 22 | - returns: self for chaining call 23 | */ 24 | func headingFilter(_ degrees:CLLocationDegrees) -> HeadingUpdateService 25 | /** 26 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instp/CLLocationManager/headingOrientation) 27 | 28 | - parameter degrees 29 | 30 | - returns: self for chaining call 31 | */ 32 | func headingOrientation(_ degrees:CLDeviceOrientation) -> HeadingUpdateService 33 | /** 34 | Should display heading calibration during monitoring heading update? 35 | 36 | - parameter should: display heading calibration 37 | 38 | - returns: self for chaining call 39 | */ 40 | func displayHeadingCalibration(_ should:Bool) -> HeadingUpdateService 41 | /// Current heading filter value 42 | var headingFilter: CLLocationDegrees{get} 43 | /// Current heading orientation value 44 | var headingOrientation: CLDeviceOrientation{get} 45 | /// Current value of displayHeadingCalibration 46 | var displayHeadingCalibration: Bool{get} 47 | } 48 | //MARK: HeadingUpdateService 49 | public protocol HeadingUpdateService: HeadingUpdateServiceConfigurable{ 50 | /// Observable of current heading update 51 | var heading: Observable{get} 52 | /** 53 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/dismissHeadingCalibrationDisplay) 54 | */ 55 | func dismissHeadingCalibrationDisplay() -> Void 56 | 57 | /** 58 | Start generating true heading with the specified parameters to start location updating 59 | 60 | - parameter withParams: setting distance filter and desired accuracy for the location update 61 | */ 62 | func startTrueHeading(_ withParams: (distanceFilter:CLLocationDistance, desiredAccuracy:CLLocationAccuracy)?) 63 | /** 64 | Stop generating true heading 65 | */ 66 | func stopTrueHeading() 67 | /** 68 | Return a cloned instance of current heading update service 69 | 70 | - returns: cloned instance 71 | */ 72 | func clone() -> HeadingUpdateService 73 | } 74 | 75 | //MARK: DefaultHeadingUpdateService 76 | class DefaultHeadingUpdateService: HeadingUpdateService { 77 | private let bridgeClass: CLLocationManagerBridge.Type 78 | var locMgr: CLLocationManagerBridge 79 | private var trueHeadingParams: (distanceFilter:CLLocationDistance, desiredAccuracy:CLLocationAccuracy)? 80 | 81 | var headingFilter: CLLocationDegrees{ 82 | get{ 83 | return locMgr.headingFilter 84 | } 85 | } 86 | var headingOrientation: CLDeviceOrientation{ 87 | get{ 88 | return locMgr.headingOrientation 89 | } 90 | } 91 | var displayHeadingCalibration: Bool{ 92 | get{ 93 | return locMgr.displayHeadingCalibration 94 | } 95 | } 96 | 97 | var observers = [(id: Int, observer: AnyObserver)]() 98 | var heading : Observable{ 99 | get{ 100 | return Observable.create { 101 | observer in 102 | var ownerService: DefaultHeadingUpdateService! = self 103 | let id = nextId() 104 | ownerService.observers.append((id, observer)) 105 | if ownerService.trueHeadingParams != nil{ 106 | ownerService.locMgr.distanceFilter = ownerService.trueHeadingParams!.distanceFilter 107 | ownerService.locMgr.desiredAccuracy = ownerService.trueHeadingParams!.desiredAccuracy 108 | ownerService.locMgr.startUpdatingLocation() 109 | } 110 | ownerService.locMgr.startUpdatingHeading() 111 | return Disposables.create { 112 | ownerService.observers.remove(at: ownerService.observers.index(where: {$0.id == id})!) 113 | if(ownerService.observers.count == 0){ 114 | ownerService.locMgr.stopUpdatingLocation() 115 | ownerService.locMgr.stopUpdatingHeading() 116 | } 117 | ownerService = nil 118 | } 119 | } 120 | } 121 | } 122 | 123 | init(bridgeClass: CLLocationManagerBridge.Type){ 124 | self.bridgeClass = bridgeClass 125 | locMgr = bridgeClass.init() 126 | locMgr.didUpdateHeading = { 127 | [weak self] 128 | mgr, heading in 129 | if let copyOfObservers = self?.observers{ 130 | for (_, observer) in copyOfObservers{ 131 | observer.onNext(heading) 132 | } 133 | } 134 | } 135 | locMgr.didFailWithError = { 136 | [weak self] 137 | mgr, err in 138 | if let copyOfObservers = self?.observers{ 139 | for (_, observer) in copyOfObservers{ 140 | observer.onError(err) 141 | } 142 | } 143 | } 144 | } 145 | 146 | func headingFilter(_ degrees: CLLocationDegrees) -> HeadingUpdateService { 147 | locMgr.headingFilter = degrees 148 | return self 149 | } 150 | 151 | func headingOrientation(_ degrees: CLDeviceOrientation) -> HeadingUpdateService { 152 | locMgr.headingOrientation = degrees 153 | return self 154 | } 155 | 156 | func displayHeadingCalibration(_ should: Bool) -> HeadingUpdateService { 157 | locMgr.displayHeadingCalibration = should 158 | return self 159 | } 160 | 161 | func startTrueHeading(_ withParams: (distanceFilter: CLLocationDistance, desiredAccuracy: CLLocationAccuracy)?) { 162 | if withParams == nil{ 163 | trueHeadingParams = (1000, kCLLocationAccuracyKilometer) 164 | }else{ 165 | trueHeadingParams = withParams 166 | } 167 | locMgr.distanceFilter = trueHeadingParams!.distanceFilter 168 | locMgr.desiredAccuracy = trueHeadingParams!.desiredAccuracy 169 | locMgr.startUpdatingLocation() 170 | 171 | } 172 | 173 | func stopTrueHeading() { 174 | locMgr.stopUpdatingLocation() 175 | } 176 | 177 | func dismissHeadingCalibrationDisplay() { 178 | locMgr.dismissHeadingCalibrationDisplay() 179 | } 180 | 181 | func clone() -> HeadingUpdateService { 182 | let clone = DefaultHeadingUpdateService(bridgeClass:bridgeClass) 183 | _ = clone.headingFilter(self.headingFilter) 184 | _ = clone.headingOrientation(self.headingOrientation) 185 | _ = clone.displayHeadingCalibration(self.displayHeadingCalibration) 186 | return clone 187 | } 188 | } 189 | 190 | #endif 191 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/MonitoringVisitsService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonitoringVisitsService.swift 3 | // RxLocationManager 4 | // 5 | // Created by Hao Yu on 16/7/6. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | #if os(iOS) 9 | import Foundation 10 | import CoreLocation 11 | import RxSwift 12 | 13 | 14 | //MARK: MonitoringVisitsService 15 | public protocol MonitoringVisitsService{ 16 | /// Observable of visit event 17 | var visiting: Observable{get} 18 | } 19 | 20 | //MARK: DefaultMonitoringVisitsService 21 | class DefaultMonitoringVisitsService: MonitoringVisitsService{ 22 | let locMgr: CLLocationManagerBridge 23 | private var observers = [(id:Int, observer: AnyObserver)]() 24 | 25 | var visiting: Observable{ 26 | get{ 27 | return Observable.create{ 28 | observer in 29 | var ownerService:DefaultMonitoringVisitsService! = self 30 | let id = nextId() 31 | ownerService.observers.append((id, observer)) 32 | ownerService.locMgr.startMonitoringVisits() 33 | return Disposables.create { 34 | ownerService.observers.remove(at: ownerService.observers.index(where: {$0.id == id})!) 35 | if ownerService.observers.count == 0{ 36 | ownerService.locMgr.stopMonitoringVisits() 37 | } 38 | ownerService = nil 39 | } 40 | } 41 | } 42 | } 43 | 44 | init(bridgeClass: CLLocationManagerBridge.Type){ 45 | locMgr = bridgeClass.init() 46 | locMgr.didVisit = { 47 | [weak self] 48 | mgr, visit in 49 | if let copyOfObservers = self?.observers{ 50 | for (_, observer) in copyOfObservers{ 51 | observer.onNext(visit) 52 | } 53 | } 54 | } 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Sources/RegionMonitoringService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegionMonitoringService.swift 3 | // RxLocationManager 4 | // 5 | // Created by Yonny Hao on 16/7/6. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | #if os(iOS) || os(OSX) 9 | import Foundation 10 | import CoreLocation 11 | import RxSwift 12 | 13 | //MARK: RegionMonitoringServiceConfigurable 14 | public protocol RegionMonitoringServiceConfigurable{ 15 | /** 16 | Unlike the official [version](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/startMonitoringForRegion:), this method allows you to start monitoring multiple regions at once 17 | 18 | - parameter regions: to start monitoring 19 | 20 | - returns: self for chaining call 21 | */ 22 | func startMonitoringForRegions(_ regions: [CLRegion]) -> RegionMonitoringService 23 | /** 24 | Unlike the official [version](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/stopMonitoringForRegion:), this method allows you to stop monitoring multiple regions at once 25 | 26 | - parameter regions: to stop monitoring 27 | 28 | - returns: self for chaining call 29 | */ 30 | func stopMonitoringForRegions(_ regions: [CLRegion]) -> RegionMonitoringService 31 | /** 32 | convenient method to stop all monitored regions at once 33 | 34 | - returns: self for chaining call 35 | */ 36 | func stopMonitoringForAllRegions() -> RegionMonitoringService 37 | /** 38 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/requestStateForRegion:) 39 | 40 | - parameter regions: to request 41 | 42 | - returns: self for chaining call 43 | */ 44 | func requestRegionsState(_ regions:[CLRegion]) -> RegionMonitoringService 45 | 46 | #if os(iOS) 47 | /** 48 | Refer to official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/startRangingBeaconsInRegion:), this method allows you to start regioning multiple beacons at once 49 | 50 | - parameter region: to start regioning 51 | 52 | - returns: self for chaining call 53 | */ 54 | func startRangingBeaconsInRegion(_ region: CLBeaconRegion) -> RegionMonitoringService 55 | /** 56 | Refer to official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/stopRangingBeaconsInRegion:) 57 | 58 | - parameter region: to stop regioning 59 | 60 | - returns: self for chaining call 61 | */ 62 | func stopRangingBeaconsInRegion(_ region: CLBeaconRegion) -> RegionMonitoringService 63 | #endif 64 | } 65 | //MARK: RegionMonitoringService 66 | public protocol RegionMonitoringService: RegionMonitoringServiceConfigurable{ 67 | /// Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instp/CLLocationManager/maximumRegionMonitoringDistance) 68 | var maximumRegionMonitoringDistance: CLLocationDistance { get } 69 | /// Observable of current monitored regions 70 | var monitoredRegions: Observable> { get } 71 | /// Observable of region entering event 72 | var entering: Observable{get} 73 | /// Observable of region exiting event 74 | var exiting: Observable{get} 75 | /// Observable of determined state of requested region 76 | var determinedRegionState: Observable<(CLRegion, CLRegionState)> {get} 77 | /// Observable of possible errors during monitoring or ranging, errors won't trigger onError on each Observable, so caller have to manage subscription lifecycle explicitly 78 | var error: Observable<(CLRegion?, NSError)>{get} 79 | 80 | 81 | #if os(iOS) 82 | /// Observable of current ranged beacons 83 | var ranging: Observable<([CLBeacon], CLBeaconRegion)>{get} 84 | /// Set of currently ranged beacon regions 85 | var rangedRegions: Set {get} 86 | #endif 87 | } 88 | 89 | //MARK: DefaultRegionMonitoringService 90 | class DefaultRegionMonitoringService: RegionMonitoringService{ 91 | let locMgr: CLLocationManagerBridge 92 | 93 | private var enteringObservers = [(id:Int, observer: AnyObserver)]() 94 | private var exitingObservers = [(id:Int, observer: AnyObserver)]() 95 | private var determinedRegionStateObservers = [(id:Int, observer: AnyObserver<(CLRegion, CLRegionState)>)]() 96 | private var errorObservers = [(id:Int, observer: AnyObserver<(CLRegion?, NSError)>)]() 97 | private var monitoredRegionsObservers = [(id:Int, observer: AnyObserver>)]() 98 | 99 | #if os(iOS) 100 | private var rangingObservers = [(id:Int, observer: AnyObserver<([CLBeacon], CLBeaconRegion)>)]() 101 | #endif 102 | 103 | var maximumRegionMonitoringDistance: CLLocationDistance{ 104 | get{ 105 | return locMgr.maximumRegionMonitoringDistance 106 | } 107 | } 108 | 109 | var entering: Observable{ 110 | get{ 111 | return Observable.create{ 112 | observer in 113 | var ownerService:DefaultRegionMonitoringService! = self 114 | let id = nextId() 115 | ownerService.enteringObservers.append((id, observer)) 116 | return Disposables.create { 117 | ownerService.enteringObservers.remove(at: ownerService.enteringObservers.index(where: {$0.id == id})!) 118 | ownerService = nil 119 | } 120 | } 121 | } 122 | } 123 | 124 | var exiting: Observable{ 125 | get{ 126 | return Observable.create{ 127 | observer in 128 | var ownerService:DefaultRegionMonitoringService! = self 129 | let id = nextId() 130 | ownerService.exitingObservers.append((id, observer)) 131 | return Disposables.create { 132 | ownerService.exitingObservers.remove(at: ownerService.exitingObservers.index(where: {$0.id == id})!) 133 | ownerService = nil 134 | } 135 | } 136 | } 137 | } 138 | 139 | var determinedRegionState: Observable<(CLRegion, CLRegionState)>{ 140 | get{ 141 | return Observable.create{ 142 | observer in 143 | var ownerService:DefaultRegionMonitoringService! = self 144 | let id = nextId() 145 | ownerService.determinedRegionStateObservers.append((id, observer)) 146 | return Disposables.create { 147 | ownerService.determinedRegionStateObservers.remove(at: ownerService.determinedRegionStateObservers.index(where: {$0.id == id})!) 148 | ownerService = nil 149 | } 150 | } 151 | } 152 | } 153 | 154 | var error: Observable<(CLRegion?, NSError)>{ 155 | get{ 156 | return Observable.create{ 157 | observer in 158 | var ownerService:DefaultRegionMonitoringService! = self 159 | let id = nextId() 160 | ownerService.errorObservers.append((id, observer)) 161 | return Disposables.create { 162 | ownerService.errorObservers.remove(at: ownerService.errorObservers.index(where: {$0.id == id})!) 163 | ownerService = nil 164 | } 165 | } 166 | } 167 | } 168 | 169 | var monitoredRegions: Observable>{ 170 | get{ 171 | return Observable.create{ 172 | observer in 173 | var ownerService:DefaultRegionMonitoringService! = self 174 | let id = nextId() 175 | ownerService.monitoredRegionsObservers.append((id, observer)) 176 | if !ownerService.locMgr.monitoredRegions.isEmpty{ 177 | observer.onNext(ownerService.locMgr.monitoredRegions) 178 | } 179 | return Disposables.create { 180 | ownerService.monitoredRegionsObservers.remove(at: ownerService.monitoredRegionsObservers.index(where:{$0.id == id})!) 181 | ownerService = nil 182 | } 183 | } 184 | } 185 | } 186 | 187 | #if os(iOS) 188 | var rangedRegions: Set { 189 | get{ 190 | return locMgr.rangedRegions as! Set 191 | } 192 | } 193 | var ranging: Observable<([CLBeacon], CLBeaconRegion)>{ 194 | get{ 195 | return Observable.create{ 196 | observer in 197 | var ownerService:DefaultRegionMonitoringService! = self 198 | let id = nextId() 199 | ownerService.rangingObservers.append((id, observer)) 200 | return Disposables.create { 201 | ownerService.rangingObservers.remove(at: ownerService.rangingObservers.index(where: {$0.id == id})!) 202 | ownerService = nil 203 | } 204 | } 205 | } 206 | } 207 | #endif 208 | 209 | init(bridgeClass: CLLocationManagerBridge.Type){ 210 | locMgr = bridgeClass.init() 211 | locMgr.didEnterRegion = { 212 | [weak self] 213 | mgr, region in 214 | if let copyOfEnteringObservers = self?.enteringObservers{ 215 | for (_, observer) in copyOfEnteringObservers{ 216 | observer.onNext(region) 217 | } 218 | } 219 | } 220 | locMgr.didExitRegion = { 221 | [weak self] 222 | mgr, region in 223 | if let copyOfExitingObservers = self?.exitingObservers{ 224 | for (_, observer) in copyOfExitingObservers{ 225 | observer.onNext(region) 226 | } 227 | } 228 | } 229 | locMgr.monitoringDidFailForRegion = { 230 | [weak self] 231 | mgr, region, error in 232 | if let copyOfErrorObservers = self?.errorObservers{ 233 | for (_, observer) in copyOfErrorObservers{ 234 | observer.onNext((region, error)) 235 | } 236 | } 237 | } 238 | locMgr.didStartMonitoringForRegion = { 239 | [weak self] 240 | mgr, region in 241 | if let copyOfMonitoredRegionsObservers = self?.monitoredRegionsObservers{ 242 | for (_, observer) in copyOfMonitoredRegionsObservers{ 243 | observer.onNext(self!.locMgr.monitoredRegions) 244 | } 245 | } 246 | } 247 | locMgr.didDetermineState = { 248 | [weak self] 249 | mgr, state, region in 250 | if let copyOfDeterminedRegionStateObservers = self?.determinedRegionStateObservers{ 251 | for(_, observer) in copyOfDeterminedRegionStateObservers{ 252 | observer.onNext((region, state)) 253 | } 254 | } 255 | } 256 | #if os(iOS) 257 | locMgr.didRangeBeaconsInRegion = { 258 | [weak self] 259 | mgr, beacons, region in 260 | if let copyOfRangingObservers = self?.rangingObservers{ 261 | for (_, observer) in copyOfRangingObservers{ 262 | observer.onNext((beacons, region)) 263 | } 264 | } 265 | } 266 | 267 | locMgr.rangingBeaconsDidFailForRegion = { 268 | [weak self] 269 | mgr, region, error in 270 | if let copyOfErrorObservers = self?.errorObservers{ 271 | for (_, observer) in copyOfErrorObservers{ 272 | observer.onNext((region, error)) 273 | } 274 | } 275 | } 276 | #endif 277 | } 278 | 279 | func requestRegionsState(_ regions: [CLRegion]) -> RegionMonitoringService { 280 | for region in regions{ 281 | locMgr.requestState(for: region) 282 | } 283 | return self 284 | } 285 | 286 | func startMonitoringForRegions(_ regions: [CLRegion]) -> RegionMonitoringService { 287 | for region in regions{ 288 | locMgr.startMonitoring(for: region) 289 | } 290 | return self 291 | } 292 | 293 | func stopMonitoringForRegions(_ regions: [CLRegion]) -> RegionMonitoringService { 294 | for region in regions{ 295 | locMgr.stopMonitoring(for: region) 296 | } 297 | 298 | //Workaround for lacking knowledge about the time when regions actually stop monitored 299 | let currentMonitoredRegions = locMgr.monitoredRegions.subtracting(regions) 300 | for (_, observer) in monitoredRegionsObservers{ 301 | observer.onNext(currentMonitoredRegions) 302 | } 303 | return self 304 | } 305 | 306 | func stopMonitoringForAllRegions() -> RegionMonitoringService { 307 | for region in locMgr.monitoredRegions{ 308 | locMgr.stopMonitoring(for: region) 309 | } 310 | return self 311 | } 312 | 313 | #if os(iOS) 314 | func startRangingBeaconsInRegion(_ region: CLBeaconRegion) -> RegionMonitoringService { 315 | locMgr.startRangingBeacons(in: region) 316 | return self 317 | } 318 | 319 | func stopRangingBeaconsInRegion(_ region: CLBeaconRegion) -> RegionMonitoringService { 320 | locMgr.stopRangingBeacons(in: region) 321 | return self 322 | } 323 | #endif 324 | } 325 | #endif 326 | -------------------------------------------------------------------------------- /Sources/RxLocationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxLocationManager.swift 3 | // PaperChat 4 | // 5 | // Created by Yonny Hao on 16/6/14. 6 | // Copyright © 2016年 HaoYu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import CoreLocation 12 | 13 | //MARK: RxLocationManager 14 | open class RxLocationManager{ 15 | private static var defaultLocationMgr: CLLocationManagerBridge = { 16 | let locMgr = CLLocationManagerBridge() 17 | locMgr.didChangeAuthorizationStatus = { 18 | clLocMgr, status in 19 | authorizationStatusSink.onNext(status) 20 | enabledSink.onNext(CLLocationManagerBridge.locationServicesEnabled()) 21 | } 22 | return locMgr 23 | }() 24 | 25 | private static var enabledSink:ReplaySubject = { 26 | let replaySubject:ReplaySubject = ReplaySubject.create(bufferSize: 1) 27 | replaySubject.onNext(CLLocationManagerBridge.locationServicesEnabled()) 28 | //Force initialize defaultLocationMgr, since it's always lazy 29 | defaultLocationMgr = defaultLocationMgr 30 | return replaySubject 31 | }() 32 | 33 | /// Observable of location service enabled status change, start with current authorization status 34 | open static var enabled:Observable{ 35 | get{ 36 | return enabledSink.distinctUntilChanged() 37 | } 38 | } 39 | 40 | private static var authorizationStatusSink:ReplaySubject = { 41 | let replaySubject:ReplaySubject = ReplaySubject.create(bufferSize: 1) 42 | replaySubject.onNext(CLLocationManagerBridge.authorizationStatus()) 43 | //Force initialize defaultLocationMgr, since it's always lazy 44 | defaultLocationMgr = defaultLocationMgr 45 | return replaySubject 46 | }() 47 | 48 | /// Observable of the app's authorization status change, start with current authorization status 49 | open static var authorizationStatus: Observable{ 50 | get{ 51 | return authorizationStatusSink.distinctUntilChanged() 52 | } 53 | } 54 | 55 | 56 | #if os(iOS) || os(watchOS) || os(tvOS) 57 | /** 58 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/requestWhenInUseAuthorization) 59 | */ 60 | open static func requestWhenInUseAuthorization(){ 61 | defaultLocationMgr.requestWhenInUseAuthorization() 62 | } 63 | #endif 64 | 65 | #if os(iOS) || os(watchOS) 66 | /** 67 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/requestAlwaysAuthorization) 68 | */ 69 | open static func requestAlwaysAuthorization(){ 70 | defaultLocationMgr.requestAlwaysAuthorization() 71 | } 72 | #endif 73 | 74 | #if os(iOS) || os(OSX) 75 | /// Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/clm/CLLocationManager/significantLocationChangeMonitoringAvailable) 76 | open static var significantLocationChangeMonitoringAvailable:Bool { 77 | get{ 78 | return CLLocationManagerBridge.significantLocationChangeMonitoringAvailable() 79 | } 80 | } 81 | 82 | /// Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/clm/CLLocationManager/headingAvailable) 83 | open static var headingAvailable:Bool{ 84 | return CLLocationManagerBridge.headingAvailable() 85 | } 86 | 87 | /** 88 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/clm/CLLocationManager/isMonitoringAvailableForClass:) 89 | 90 | - parameter regionClass: to test 91 | 92 | - returns: self for chaining call 93 | */ 94 | open static func isMonitoringAvailableForClass(regionClass: AnyClass) -> Bool{ 95 | return CLLocationManagerBridge.isMonitoringAvailable(for: regionClass) 96 | } 97 | #endif 98 | 99 | #if os(iOS) 100 | /// Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/clm/CLLocationManager/deferredLocationUpdatesAvailable) 101 | open static var deferredLocationUpdatesAvailable:Bool{ 102 | get{ 103 | return CLLocationManagerBridge.deferredLocationUpdatesAvailable() 104 | } 105 | } 106 | 107 | /// Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/clm/CLLocationManager/isRangingAvailable) 108 | open static var isRangingAvailable:Bool{ 109 | get{ 110 | return CLLocationManagerBridge.isRangingAvailable() 111 | } 112 | } 113 | #endif 114 | 115 | /// Shared standard location service 116 | open static let Standard: StandardLocationService = DefaultStandardLocationService(bridgeClass: CLLocationManagerBridge.self) 117 | 118 | #if os(iOS) || os(OSX) 119 | /// Shared significant location update service 120 | open static let SignificantLocation: SignificantLocationUpdateService = DefaultSignificantLocationUpdateService(bridgeClass: CLLocationManagerBridge.self) 121 | 122 | /// Shared region monitoring service 123 | open static let RegionMonitoring: RegionMonitoringService = DefaultRegionMonitoringService(bridgeClass: CLLocationManagerBridge.self) 124 | #endif 125 | 126 | #if os(iOS) 127 | /// Shared visit monitoring service 128 | open static let VisitMonitoring: MonitoringVisitsService = DefaultMonitoringVisitsService(bridgeClass: CLLocationManagerBridge.self) 129 | /// Shared heading update service 130 | open static let HeadingUpdate: HeadingUpdateService = DefaultHeadingUpdateService(bridgeClass: CLLocationManagerBridge.self) 131 | #endif 132 | } 133 | 134 | -------------------------------------------------------------------------------- /Sources/SignificantLocationUpdateService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignificantLocationUpdateService.swift 3 | // RxLocationManager 4 | // 5 | // Created by Hao Yu on 16/7/6. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | #if os(iOS) || os(OSX) 9 | import Foundation 10 | import CoreLocation 11 | import RxSwift 12 | 13 | //MARK: SignificantLocationUpdateService 14 | public protocol SignificantLocationUpdateService{ 15 | /// Observable of current significant location change 16 | var locating: Observable<[CLLocation]> {get} 17 | } 18 | 19 | //MARK: DefaultSignificantLocationUpdateService 20 | class DefaultSignificantLocationUpdateService: SignificantLocationUpdateService{ 21 | let locMgr:CLLocationManagerBridge 22 | private var observers = [(id: Int, AnyObserver<[CLLocation]>)]() 23 | var locating: Observable<[CLLocation]>{ 24 | get{ 25 | return Observable.create { 26 | observer in 27 | var ownerService: DefaultSignificantLocationUpdateService! = self 28 | let id = nextId() 29 | ownerService.observers.append((id, observer)) 30 | ownerService.locMgr.startMonitoringSignificantLocationChanges() 31 | return Disposables.create { 32 | ownerService.observers.remove(at: ownerService.observers.index(where: {$0.id == id})!) 33 | if(ownerService.observers.count == 0){ 34 | ownerService.locMgr.stopMonitoringSignificantLocationChanges() 35 | } 36 | ownerService = nil 37 | } 38 | } 39 | } 40 | } 41 | 42 | init(bridgeClass: CLLocationManagerBridge.Type){ 43 | locMgr = bridgeClass.init() 44 | locMgr.didUpdateLocations = { 45 | [weak self] 46 | mgr, locations in 47 | if let copyOfObservers = self?.observers{ 48 | for (_,observer) in copyOfObservers{ 49 | observer.onNext(locations) 50 | } 51 | } 52 | } 53 | locMgr.didFailWithError = { 54 | [weak self] 55 | mgr, err in 56 | if let copyOfObservers = self?.observers{ 57 | for (_,observer) in copyOfObservers{ 58 | observer.onError(err) 59 | } 60 | } 61 | } 62 | } 63 | } 64 | #endif 65 | -------------------------------------------------------------------------------- /Sources/StandardLocationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardLocationService.swift 3 | // RxLocationManager 4 | // 5 | // Created by Hao Yu on 16/7/6. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | import Foundation 9 | import CoreLocation 10 | import RxSwift 11 | 12 | 13 | //MARK: StandardLocationServiceConfigurable 14 | public protocol StandardLocationServiceConfigurable{ 15 | /** 16 | Set distance filter 17 | 18 | - parameter distance 19 | 20 | - returns: self for chaining call 21 | */ 22 | func distanceFilter(_ distance: CLLocationDistance) -> StandardLocationService 23 | /** 24 | Set desired accuracy 25 | 26 | - parameter desiredAccuracy 27 | 28 | - returns: self for chaining call 29 | */ 30 | func desiredAccuracy(_ desiredAccuracy: CLLocationAccuracy) -> StandardLocationService 31 | 32 | #if os(iOS) 33 | /** 34 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/allowDeferredLocationUpdatesUntilTraveled:timeout:) 35 | - parameter distance 36 | - parameter timeout 37 | 38 | - returns: self for chaining call 39 | */ 40 | func allowDeferredLocationUpdates(untilTraveled distance: CLLocationDistance, timeout: TimeInterval) -> StandardLocationService 41 | /** 42 | Refer description in official [document](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/disallowDeferredLocationUpdates) 43 | 44 | - returns: self for chaining call 45 | */ 46 | func disallowDeferredLocationUpdates() -> StandardLocationService 47 | /** 48 | Set Boolean value to [pausesLocationUpdatesAutomatically](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instp/CLLocationManager/pausesLocationUpdatesAutomatically) 49 | 50 | - parameter pause: Boolean value 51 | 52 | - returns: self for chaining call 53 | */ 54 | func pausesLocationUpdatesAutomatically(_ pause : Bool) -> StandardLocationService 55 | 56 | 57 | /** 58 | Set Boolean value to [allowsBackgroundLocationUpdates](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instp/CLLocationManager/allowsBackgroundLocationUpdates) 59 | 60 | - parameter allow: Boolean value 61 | 62 | - returns: self for chaining call 63 | */ 64 | @available(iOS 9.0, *) 65 | func allowsBackgroundLocationUpdates(_ allow : Bool) -> StandardLocationService 66 | /** 67 | Set value to [activityType](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instp/CLLocationManager/activityType) 68 | 69 | - parameter type 70 | 71 | - returns: self for chaining call 72 | */ 73 | func activityType(_ type: CLActivityType) -> StandardLocationService 74 | #endif 75 | 76 | /// Current distance filter value 77 | var distanceFilter: CLLocationDistance {get} 78 | /// Current desired accuracy value 79 | var desiredAccuracy: CLLocationAccuracy {get} 80 | 81 | #if os(iOS) 82 | /// Current pausesLocationUpdatesAutomatically value 83 | var pausesLocationUpdatesAutomatically: Bool {get} 84 | @available(iOS 9.0, *) 85 | /// Current allowsBackgroundLocationUpdates 86 | var allowsBackgroundLocationUpdates: Bool {get} 87 | /// Current activityType 88 | var activityType: CLActivityType {get} 89 | #endif 90 | } 91 | 92 | 93 | //MARK: StandardLocationService 94 | public protocol StandardLocationService: StandardLocationServiceConfigurable{ 95 | #if os(iOS) || os(OSX) || os(watchOS) 96 | /// Observable of current changing location, series of CLLocation objects will be reported, intermittent LocationUnknown error will be ignored and not stop subscriptions on this observable, other errors are reported as usual 97 | @available(watchOS 3.0, *) 98 | var locating: Observable<[CLLocation]>{get} 99 | #endif 100 | 101 | #if os(iOS) || os(watchOS) || os(tvOS) 102 | /// Observable of current location, only report one CLLocation object and complete, or error if underlying CLLocationManager reports error 103 | var located: Observable{get} 104 | #endif 105 | 106 | #if os(iOS) 107 | /// Observable of possible error when calling allowDeferredLocationUpdates method 108 | var deferredUpdateFinished: Observable{get} 109 | /// Observable of pause status 110 | var isPaused: Observable{get} 111 | #endif 112 | 113 | /** 114 | Return cloned instance 115 | 116 | - returns: cloned standard location service 117 | */ 118 | func clone() -> StandardLocationService 119 | } 120 | 121 | //MARK: DefaultStandardLocationService 122 | class DefaultStandardLocationService: StandardLocationService{ 123 | private let bridgeClass: CLLocationManagerBridge.Type 124 | 125 | #if os(iOS) || os(watchOS) || os(tvOS) 126 | var locMgrForLocation:CLLocationManagerBridge! 127 | private var locatedObservers = [(id: Int, observer: AnyObserver)]() 128 | #endif 129 | 130 | #if os(iOS) || os(OSX) || os(watchOS) 131 | var locMgrForLocating:CLLocationManagerBridge! 132 | private var locatingObservers = [(id: Int, observer: AnyObserver<[CLLocation]>)]() 133 | #endif 134 | 135 | #if os(iOS) 136 | private var deferredUpdateErrorObservers = [(id: Int, observer: AnyObserver)]() 137 | private var isPausedObservers = [(id: Int, observer: AnyObserver)]() 138 | #endif 139 | 140 | var distanceFilter:CLLocationDistance{ 141 | get{ 142 | #if os(OSX) 143 | return locMgrForLocating.distanceFilter 144 | #else 145 | return locMgrForLocation.distanceFilter 146 | #endif 147 | } 148 | } 149 | var desiredAccuracy: CLLocationAccuracy{ 150 | get{ 151 | #if os(OSX) 152 | return locMgrForLocating.desiredAccuracy 153 | #else 154 | return locMgrForLocation.desiredAccuracy 155 | #endif 156 | } 157 | } 158 | 159 | #if os(iOS) 160 | var pausesLocationUpdatesAutomatically: Bool{ 161 | get{ 162 | return locMgrForLocating.pausesLocationUpdatesAutomatically 163 | } 164 | } 165 | 166 | @available(iOS 9.0, *) 167 | var allowsBackgroundLocationUpdates: Bool{ 168 | get{ 169 | return locMgrForLocating.allowsBackgroundLocationUpdates 170 | } 171 | } 172 | var activityType: CLActivityType{ 173 | get{ 174 | return locMgrForLocating.activityType 175 | } 176 | } 177 | #endif 178 | 179 | #if os(iOS) || os(watchOS) || os(tvOS) 180 | var located:Observable { 181 | get{ 182 | if self.locMgrForLocation.location != nil{ 183 | return Observable.just(self.locMgrForLocation.location!) 184 | }else{ 185 | return Observable.create{ 186 | observer in 187 | var ownerService: DefaultStandardLocationService! = self 188 | let id = nextId() 189 | ownerService.locatedObservers.append((id, observer)) 190 | if #available(iOS 9.0, *) { 191 | ownerService.locMgrForLocation.requestLocation() 192 | } else { 193 | #if os(iOS) 194 | ownerService.locMgrForLocation.startUpdatingLocation() 195 | #endif 196 | } 197 | return Disposables.create { 198 | ownerService.locatedObservers.remove(at: ownerService.locatedObservers.index(where: {$0.id == id})!) 199 | if(ownerService.locatedObservers.count == 0){ 200 | ownerService.locMgrForLocation.stopUpdatingLocation() 201 | } 202 | ownerService = nil 203 | } 204 | } 205 | } 206 | } 207 | } 208 | #endif 209 | 210 | #if os(iOS) 211 | var isPaused:Observable{ 212 | get{ 213 | return Observable.create { 214 | observer in 215 | var ownerService: DefaultStandardLocationService! = self 216 | let id = nextId() 217 | ownerService.isPausedObservers.append((id, observer)) 218 | return Disposables.create { 219 | ownerService.isPausedObservers.remove(at: ownerService.isPausedObservers.index(where: {$0.id == id})!) 220 | ownerService = nil 221 | } 222 | } 223 | } 224 | } 225 | 226 | var deferredUpdateFinished:Observable{ 227 | get{ 228 | return Observable.create { 229 | observer in 230 | var ownerService: DefaultStandardLocationService! = self 231 | let id = nextId() 232 | ownerService.deferredUpdateErrorObservers.append((id, observer)) 233 | return Disposables.create { 234 | ownerService.deferredUpdateErrorObservers.remove(at: ownerService.deferredUpdateErrorObservers.index(where: {$0.id == id})!) 235 | ownerService = nil 236 | } 237 | } 238 | } 239 | } 240 | #endif 241 | 242 | #if os(iOS) || os(OSX) || os(watchOS) 243 | @available(watchOS 3.0, *) 244 | var locating:Observable<[CLLocation]> { 245 | get{ 246 | return Observable.create { 247 | observer in 248 | var ownerService: DefaultStandardLocationService! = self 249 | let id = nextId() 250 | ownerService.locatingObservers.append((id, observer)) 251 | //calling this method to start updating location anyway, it's no harm according to the doc 252 | ownerService.locMgrForLocating.startUpdatingLocation() 253 | return Disposables.create { 254 | ownerService.locatingObservers.remove(at: ownerService.locatingObservers.index(where: {$0.id == id})!) 255 | if(ownerService.locatingObservers.count == 0){ 256 | ownerService.locMgrForLocating.stopUpdatingLocation() 257 | } 258 | ownerService = nil 259 | } 260 | } 261 | } 262 | } 263 | #endif 264 | 265 | 266 | init(bridgeClass: CLLocationManagerBridge.Type){ 267 | self.bridgeClass = bridgeClass 268 | #if os(iOS) || os(watchOS) || os(tvOS) 269 | locMgrForLocation = bridgeClass.init() 270 | #endif 271 | 272 | #if os(iOS) || os(OSX) || os(watchOS) 273 | if #available(watchOS 3.0, *) { 274 | locMgrForLocating = bridgeClass.init() 275 | } 276 | #endif 277 | 278 | #if os(iOS) || os(watchOS) || os(tvOS) 279 | locMgrForLocation.didUpdateLocations = { 280 | [weak self] 281 | mgr, locations in 282 | if let copyOfLocatedObservers = self?.locatedObservers{ 283 | for (_, observer) in copyOfLocatedObservers{ 284 | observer.onNext(locations.last!) 285 | observer.onCompleted() 286 | } 287 | guard #available(iOS 9.0, *) else { 288 | self?.locMgrForLocation.stopUpdatingLocation() 289 | return 290 | } 291 | 292 | } 293 | } 294 | locMgrForLocation.didFailWithError = { 295 | [weak self] 296 | mgr, err in 297 | if let copyOfLocatedObservers = self?.locatedObservers{ 298 | for (_, observer) in copyOfLocatedObservers{ 299 | observer.onError(err) 300 | } 301 | } 302 | } 303 | #endif 304 | 305 | #if os(iOS) || os(OSX) || os(watchOS) 306 | if #available(watchOS 3.0, *){ 307 | locMgrForLocating.didUpdateLocations = { 308 | [weak self] 309 | mgr, locations in 310 | if let copyOfLocatingObservers = self?.locatingObservers{ 311 | for (_, observer) in copyOfLocatingObservers{ 312 | observer.onNext(locations) 313 | } 314 | } 315 | } 316 | 317 | locMgrForLocating.didFailWithError = { 318 | [weak self] 319 | mgr, err in 320 | if err.domain == "kCLErrorDomain" && CLError.locationUnknown.rawValue == err.code{ 321 | //ignore location update error, since new update event may come 322 | return 323 | } 324 | if let copyOfLocatingObservers = self?.locatingObservers{ 325 | for (_, observer) in copyOfLocatingObservers{ 326 | observer.onError(err) 327 | } 328 | } 329 | } 330 | } 331 | #endif 332 | 333 | 334 | #if os(iOS) 335 | locMgrForLocating.didFinishDeferredUpdatesWithError = { 336 | [weak self] 337 | mgr, error in 338 | if let copyOfdeferredUpdateErrorObservers = self?.deferredUpdateErrorObservers{ 339 | for (_, observer) in copyOfdeferredUpdateErrorObservers{ 340 | observer.onNext(error) 341 | } 342 | } 343 | } 344 | 345 | locMgrForLocating.didPausedUpdate = { 346 | [weak self] 347 | mgr in 348 | if let copyOfIsPausedObservers = self?.isPausedObservers{ 349 | for(_, observer) in copyOfIsPausedObservers{ 350 | observer.onNext(true) 351 | } 352 | } 353 | } 354 | locMgrForLocating.didResumeUpdate = { 355 | [weak self] 356 | mgr in 357 | if let copyOfIsPausedObservers = self?.isPausedObservers{ 358 | for(_, observer) in copyOfIsPausedObservers{ 359 | observer.onNext(false) 360 | } 361 | } 362 | } 363 | #endif 364 | } 365 | 366 | #if os(iOS) 367 | func allowDeferredLocationUpdates(untilTraveled distance: CLLocationDistance, timeout: TimeInterval) -> StandardLocationService{ 368 | locMgrForLocating.allowDeferredLocationUpdates(untilTraveled: distance, timeout: timeout) 369 | return self 370 | } 371 | 372 | func disallowDeferredLocationUpdates() -> StandardLocationService{ 373 | locMgrForLocating.disallowDeferredLocationUpdates() 374 | return self 375 | } 376 | 377 | func pausesLocationUpdatesAutomatically(_ pause: Bool) -> StandardLocationService { 378 | locMgrForLocating.pausesLocationUpdatesAutomatically = pause 379 | return self 380 | } 381 | 382 | @available(iOS 9.0, *) 383 | func allowsBackgroundLocationUpdates(_ allow: Bool) -> StandardLocationService { 384 | locMgrForLocating.allowsBackgroundLocationUpdates = allow 385 | return self 386 | } 387 | 388 | func activityType(_ type: CLActivityType) -> StandardLocationService { 389 | locMgrForLocating.activityType = type 390 | return self 391 | } 392 | #endif 393 | 394 | func distanceFilter(_ distance: CLLocationDistance) -> StandardLocationService { 395 | #if os(iOS) || os(watchOS) || os(tvOS) 396 | locMgrForLocation.distanceFilter = distance 397 | #endif 398 | 399 | #if os(iOS) || os(OSX) 400 | locMgrForLocating.distanceFilter = distance 401 | #endif 402 | return self 403 | } 404 | 405 | func desiredAccuracy(_ desiredAccuracy: CLLocationAccuracy) -> StandardLocationService { 406 | #if os(iOS) || os(watchOS) || os(tvOS) 407 | locMgrForLocation.desiredAccuracy = desiredAccuracy 408 | #endif 409 | 410 | #if os(iOS) || os(OSX) 411 | locMgrForLocating.desiredAccuracy = desiredAccuracy 412 | #endif 413 | return self 414 | } 415 | 416 | func clone() -> StandardLocationService { 417 | let cloned = DefaultStandardLocationService(bridgeClass: bridgeClass) 418 | #if os(iOS) 419 | _ = cloned.activityType(self.activityType) 420 | if #available(iOS 9.0, *) { 421 | _ = cloned.allowsBackgroundLocationUpdates(self.allowsBackgroundLocationUpdates) 422 | } 423 | _ = cloned.pausesLocationUpdatesAutomatically(self.pausesLocationUpdatesAutomatically) 424 | #endif 425 | 426 | _ = cloned.desiredAccuracy(self.desiredAccuracy) 427 | _ = cloned.distanceFilter(self.distanceFilter) 428 | 429 | return cloned 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /Sources/nextId.swift: -------------------------------------------------------------------------------- 1 | // 2 | // nextId.swift 3 | // RxLocationManager 4 | // 5 | // Created by Hao Yu on 16/7/6. 6 | // Copyright © 2016年 GFWGTH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private var id = -1 12 | func nextId() -> Int{ 13 | id += 1 14 | return id 15 | } --------------------------------------------------------------------------------