@capacitor-community/background-geolocation
Advanced tools
| import Capacitor | ||
| import Foundation | ||
| import UIKit | ||
| import CoreLocation | ||
| // Avoids a bewildering type warning. | ||
| let null = Optional<Double>.none as Any | ||
| func formatLocation(_ location: CLLocation) -> PluginCallResultData { | ||
| var simulated = false; | ||
| if #available(iOS 15, *) { | ||
| // Prior to iOS 15, it was not possible to detect simulated locations. | ||
| // But in general, it is very difficult to simulate locations on iOS in | ||
| // production. | ||
| if location.sourceInformation != nil { | ||
| simulated = location.sourceInformation!.isSimulatedBySoftware; | ||
| } | ||
| } | ||
| return [ | ||
| "latitude": location.coordinate.latitude, | ||
| "longitude": location.coordinate.longitude, | ||
| "accuracy": location.horizontalAccuracy, | ||
| "altitude": location.altitude, | ||
| "altitudeAccuracy": location.verticalAccuracy, | ||
| "simulated": simulated, | ||
| "speed": location.speed < 0 ? null : location.speed, | ||
| "bearing": location.course < 0 ? null : location.course, | ||
| "time": NSNumber( | ||
| value: Int( | ||
| location.timestamp.timeIntervalSince1970 * 1000 | ||
| ) | ||
| ), | ||
| ] | ||
| } | ||
| class Watcher { | ||
| let callbackId: String | ||
| let locationManager: CLLocationManager = CLLocationManager() | ||
| private let created = Date() | ||
| private let allowStale: Bool | ||
| private var isUpdatingLocation: Bool = false | ||
| init(_ id: String, stale: Bool) { | ||
| callbackId = id | ||
| allowStale = stale | ||
| } | ||
| func start() { | ||
| // Avoid unnecessary calls to startUpdatingLocation, which can | ||
| // result in extraneous invocations of didFailWithError. | ||
| if !isUpdatingLocation { | ||
| locationManager.startUpdatingLocation() | ||
| isUpdatingLocation = true | ||
| } | ||
| } | ||
| func stop() { | ||
| if isUpdatingLocation { | ||
| locationManager.stopUpdatingLocation() | ||
| isUpdatingLocation = false | ||
| } | ||
| } | ||
| func isLocationValid(_ location: CLLocation) -> Bool { | ||
| return ( | ||
| allowStale || | ||
| location.timestamp >= created | ||
| ) | ||
| } | ||
| } | ||
| @objc(BackgroundGeolocation) | ||
| public class BackgroundGeolocation: CAPPlugin, | ||
| CAPBridgedPlugin, | ||
| CLLocationManagerDelegate { | ||
| private var watchers = [Watcher]() | ||
| public let identifier = "BackgroundGeolocation" | ||
| public let jsName = "BackgroundGeolocation" | ||
| public let pluginMethods: [CAPPluginMethod] = [ | ||
| CAPPluginMethod(name: "addWatcher", returnType: CAPPluginReturnCallback), | ||
| CAPPluginMethod(name: "removeWatcher", returnType: CAPPluginReturnPromise), | ||
| CAPPluginMethod(name: "openSettings", returnType: CAPPluginReturnPromise) | ||
| ] | ||
| @objc public override func load() { | ||
| UIDevice.current.isBatteryMonitoringEnabled = true | ||
| } | ||
| @objc func addWatcher(_ call: CAPPluginCall) { | ||
| call.keepAlive = true | ||
| // CLLocationManager requires main thread | ||
| DispatchQueue.main.async { | ||
| let background = call.getString("backgroundMessage") != nil | ||
| let watcher = Watcher( | ||
| call.callbackId, | ||
| stale: call.getBool("stale") ?? false | ||
| ) | ||
| let manager = watcher.locationManager | ||
| manager.delegate = self | ||
| let externalPower = [ | ||
| .full, | ||
| .charging | ||
| ].contains(UIDevice.current.batteryState) | ||
| manager.desiredAccuracy = ( | ||
| externalPower | ||
| ? kCLLocationAccuracyBestForNavigation | ||
| : kCLLocationAccuracyBest | ||
| ) | ||
| var distanceFilter = call.getDouble("distanceFilter") | ||
| // It appears that setting manager.distanceFilter to 0 can prevent | ||
| // subsequent location updates. See issue #88. | ||
| if distanceFilter == nil || distanceFilter == 0 { | ||
| distanceFilter = kCLDistanceFilterNone | ||
| } | ||
| manager.distanceFilter = distanceFilter! | ||
| manager.allowsBackgroundLocationUpdates = background | ||
| manager.showsBackgroundLocationIndicator = background | ||
| manager.pausesLocationUpdatesAutomatically = false | ||
| self.watchers.append(watcher) | ||
| if call.getBool("requestPermissions") != false { | ||
| let status = CLLocationManager.authorizationStatus() | ||
| if [ | ||
| .notDetermined, | ||
| .denied, | ||
| .restricted, | ||
| ].contains(status) { | ||
| return ( | ||
| background | ||
| ? manager.requestAlwaysAuthorization() | ||
| : manager.requestWhenInUseAuthorization() | ||
| ) | ||
| } | ||
| if ( | ||
| background && status == .authorizedWhenInUse | ||
| ) { | ||
| // Attempt to escalate. | ||
| manager.requestAlwaysAuthorization() | ||
| } | ||
| } | ||
| return watcher.start() | ||
| } | ||
| } | ||
| @objc func removeWatcher(_ call: CAPPluginCall) { | ||
| // CLLocationManager requires main thread | ||
| DispatchQueue.main.async { | ||
| if let callbackId = call.getString("id") { | ||
| if let index = self.watchers.firstIndex( | ||
| where: { $0.callbackId == callbackId } | ||
| ) { | ||
| self.watchers[index].locationManager.stopUpdatingLocation() | ||
| self.watchers.remove(at: index) | ||
| } | ||
| if let savedCall = self.bridge?.savedCall(withID: callbackId) { | ||
| self.bridge?.releaseCall(savedCall) | ||
| } | ||
| return call.resolve() | ||
| } | ||
| return call.reject("No callback ID") | ||
| } | ||
| } | ||
| @objc func openSettings(_ call: CAPPluginCall) { | ||
| DispatchQueue.main.async { | ||
| guard let settingsUrl = URL( | ||
| string: UIApplication.openSettingsURLString | ||
| ) else { | ||
| return call.reject("No link to settings available") | ||
| } | ||
| if UIApplication.shared.canOpenURL(settingsUrl) { | ||
| UIApplication.shared.open(settingsUrl, completionHandler: { | ||
| (success) in | ||
| if (success) { | ||
| return call.resolve() | ||
| } else { | ||
| return call.reject("Failed to open settings") | ||
| } | ||
| }) | ||
| } else { | ||
| return call.reject("Cannot open settings") | ||
| } | ||
| } | ||
| } | ||
| public func locationManager( | ||
| _ manager: CLLocationManager, | ||
| didFailWithError error: Error | ||
| ) { | ||
| if let watcher = self.watchers.first( | ||
| where: { $0.locationManager == manager } | ||
| ) { | ||
| if let call = self.bridge?.savedCall(withID: watcher.callbackId) { | ||
| if let clErr = error as? CLError { | ||
| if clErr.code == .locationUnknown { | ||
| // This error is sometimes sent by the manager if | ||
| // it cannot get a fix immediately. | ||
| return | ||
| } else if (clErr.code == .denied) { | ||
| watcher.stop() | ||
| return call.reject( | ||
| "Permission denied.", | ||
| "NOT_AUTHORIZED" | ||
| ) | ||
| } | ||
| } | ||
| return call.reject(error.localizedDescription, nil, error) | ||
| } | ||
| } | ||
| } | ||
| public func locationManager( | ||
| _ manager: CLLocationManager, | ||
| didUpdateLocations locations: [CLLocation] | ||
| ) { | ||
| if let location = locations.last { | ||
| if let watcher = self.watchers.first( | ||
| where: { $0.locationManager == manager } | ||
| ) { | ||
| if watcher.isLocationValid(location) { | ||
| if let call = self.bridge?.savedCall(withID: watcher.callbackId) { | ||
| return call.resolve(formatLocation(location)) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| public func locationManager( | ||
| _ manager: CLLocationManager, | ||
| didChangeAuthorization status: CLAuthorizationStatus | ||
| ) { | ||
| // If this method is called before the user decides on a permission, as | ||
| // it is on iOS 14 when the permissions dialog is presented, we ignore | ||
| // it. | ||
| if status != .notDetermined { | ||
| if let watcher = self.watchers.first( | ||
| where: { $0.locationManager == manager } | ||
| ) { | ||
| return watcher.start() | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // swift-tools-version: 5.9 | ||
| import PackageDescription | ||
| let package = Package( | ||
| name: "CapacitorCommunityBackgroundGeolocation", | ||
| platforms: [.iOS(.v12)], | ||
| products: [ | ||
| .library( | ||
| name: "CapacitorCommunityBackgroundGeolocation", | ||
| targets: ["BackgroundGeolocationPlugin"] | ||
| ) | ||
| ], | ||
| dependencies: [ | ||
| .package( | ||
| url: "https://github.com/ionic-team/capacitor-swift-pm.git", | ||
| from: "7.0.0" | ||
| ) | ||
| ], | ||
| targets: [ | ||
| .target( | ||
| name: "BackgroundGeolocationPlugin", | ||
| dependencies: [ | ||
| .product(name: "Capacitor", package: "capacitor-swift-pm"), | ||
| .product(name: "Cordova", package: "capacitor-swift-pm") | ||
| ], | ||
| path: "ios/Plugin/Swift" | ||
| ) | ||
| ] | ||
| ) |
+13
-11
| { | ||
| "name": "@capacitor-community/background-geolocation", | ||
| "version": "1.2.25", | ||
| "version": "1.2.26", | ||
| "description": "Receive geolocation updates even while the app is in the background.", | ||
@@ -13,18 +13,20 @@ "repository": { | ||
| "files": [ | ||
| "CapacitorCommunityBackgroundGeolocation.podspec", | ||
| "Package.swift", | ||
| "android/build.gradle", | ||
| "android/gradle.properties", | ||
| "android/gradle/wrapper/gradle-wrapper.properties", | ||
| "android/proguard-rules.pro", | ||
| "android/settings.gradle", | ||
| "android/proguard-rules.pro", | ||
| "android/gradle/wrapper/gradle-wrapper.properties", | ||
| "android/gradle.properties", | ||
| "ios/Plugin/Plugin.*", | ||
| "android/src/main/", | ||
| "ios/Podfile*", | ||
| "definitions.d.ts", | ||
| "ios/Plugin/Info.plist", | ||
| "CapacitorCommunityBackgroundGeolocation.podspec", | ||
| "definitions.d.ts" | ||
| "ios/Plugin/Plugin.*", | ||
| "ios/Plugin/Swift/Plugin.swift", | ||
| "ios/Podfile*" | ||
| ], | ||
| "devDependencies": { | ||
| "@capacitor/android": "6.0.0-rc.0", | ||
| "@capacitor/core": "6.0.0-rc.0", | ||
| "@capacitor/ios": "6.0.0-rc.0" | ||
| "@capacitor/android": "^7.0.0", | ||
| "@capacitor/core": "^7.0.0", | ||
| "@capacitor/ios": "^7.0.0" | ||
| }, | ||
@@ -31,0 +33,0 @@ "peerDependencies": { |
+4
-1
@@ -217,3 +217,6 @@ # Background Geolocation | ||
| ### v1.2.17 | ||
| ### v1.2.26 | ||
| - Add support for Swift Package Manager (SPM). | ||
| ### v1.2.25 | ||
| - Add support for Capacitor v7. | ||
@@ -220,0 +223,0 @@ |
| import Capacitor | ||
| import Foundation | ||
| import UIKit | ||
| import CoreLocation | ||
| // Avoids a bewildering type warning. | ||
| let null = Optional<Double>.none as Any | ||
| func formatLocation(_ location: CLLocation) -> PluginCallResultData { | ||
| var simulated = false; | ||
| if #available(iOS 15, *) { | ||
| // Prior to iOS 15, it was not possible to detect simulated locations. | ||
| // But in general, it is very difficult to simulate locations on iOS in | ||
| // production. | ||
| if location.sourceInformation != nil { | ||
| simulated = location.sourceInformation!.isSimulatedBySoftware; | ||
| } | ||
| } | ||
| return [ | ||
| "latitude": location.coordinate.latitude, | ||
| "longitude": location.coordinate.longitude, | ||
| "accuracy": location.horizontalAccuracy, | ||
| "altitude": location.altitude, | ||
| "altitudeAccuracy": location.verticalAccuracy, | ||
| "simulated": simulated, | ||
| "speed": location.speed < 0 ? null : location.speed, | ||
| "bearing": location.course < 0 ? null : location.course, | ||
| "time": NSNumber( | ||
| value: Int( | ||
| location.timestamp.timeIntervalSince1970 * 1000 | ||
| ) | ||
| ), | ||
| ] | ||
| } | ||
| class Watcher { | ||
| let callbackId: String | ||
| let locationManager: CLLocationManager = CLLocationManager() | ||
| private let created = Date() | ||
| private let allowStale: Bool | ||
| private var isUpdatingLocation: Bool = false | ||
| init(_ id: String, stale: Bool) { | ||
| callbackId = id | ||
| allowStale = stale | ||
| } | ||
| func start() { | ||
| // Avoid unnecessary calls to startUpdatingLocation, which can | ||
| // result in extraneous invocations of didFailWithError. | ||
| if !isUpdatingLocation { | ||
| locationManager.startUpdatingLocation() | ||
| isUpdatingLocation = true | ||
| } | ||
| } | ||
| func stop() { | ||
| if isUpdatingLocation { | ||
| locationManager.stopUpdatingLocation() | ||
| isUpdatingLocation = false | ||
| } | ||
| } | ||
| func isLocationValid(_ location: CLLocation) -> Bool { | ||
| return ( | ||
| allowStale || | ||
| location.timestamp >= created | ||
| ) | ||
| } | ||
| } | ||
| @objc(BackgroundGeolocation) | ||
| public class BackgroundGeolocation : CAPPlugin, CLLocationManagerDelegate { | ||
| private var watchers = [Watcher]() | ||
| @objc public override func load() { | ||
| UIDevice.current.isBatteryMonitoringEnabled = true | ||
| } | ||
| @objc func addWatcher(_ call: CAPPluginCall) { | ||
| call.keepAlive = true | ||
| // CLLocationManager requires main thread | ||
| DispatchQueue.main.async { | ||
| let background = call.getString("backgroundMessage") != nil | ||
| let watcher = Watcher( | ||
| call.callbackId, | ||
| stale: call.getBool("stale") ?? false | ||
| ) | ||
| let manager = watcher.locationManager | ||
| manager.delegate = self | ||
| let externalPower = [ | ||
| .full, | ||
| .charging | ||
| ].contains(UIDevice.current.batteryState) | ||
| manager.desiredAccuracy = ( | ||
| externalPower | ||
| ? kCLLocationAccuracyBestForNavigation | ||
| : kCLLocationAccuracyBest | ||
| ) | ||
| var distanceFilter = call.getDouble("distanceFilter") | ||
| // It appears that setting manager.distanceFilter to 0 can prevent | ||
| // subsequent location updates. See issue #88. | ||
| if distanceFilter == nil || distanceFilter == 0 { | ||
| distanceFilter = kCLDistanceFilterNone | ||
| } | ||
| manager.distanceFilter = distanceFilter! | ||
| manager.allowsBackgroundLocationUpdates = background | ||
| manager.showsBackgroundLocationIndicator = background | ||
| manager.pausesLocationUpdatesAutomatically = false | ||
| self.watchers.append(watcher) | ||
| if call.getBool("requestPermissions") != false { | ||
| let status = CLLocationManager.authorizationStatus() | ||
| if [ | ||
| .notDetermined, | ||
| .denied, | ||
| .restricted, | ||
| ].contains(status) { | ||
| return ( | ||
| background | ||
| ? manager.requestAlwaysAuthorization() | ||
| : manager.requestWhenInUseAuthorization() | ||
| ) | ||
| } | ||
| if ( | ||
| background && status == .authorizedWhenInUse | ||
| ) { | ||
| // Attempt to escalate. | ||
| manager.requestAlwaysAuthorization() | ||
| } | ||
| } | ||
| return watcher.start() | ||
| } | ||
| } | ||
| @objc func removeWatcher(_ call: CAPPluginCall) { | ||
| // CLLocationManager requires main thread | ||
| DispatchQueue.main.async { | ||
| if let callbackId = call.getString("id") { | ||
| if let index = self.watchers.firstIndex( | ||
| where: { $0.callbackId == callbackId } | ||
| ) { | ||
| self.watchers[index].locationManager.stopUpdatingLocation() | ||
| self.watchers.remove(at: index) | ||
| } | ||
| if let savedCall = self.bridge?.savedCall(withID: callbackId) { | ||
| self.bridge?.releaseCall(savedCall) | ||
| } | ||
| return call.resolve() | ||
| } | ||
| return call.reject("No callback ID") | ||
| } | ||
| } | ||
| @objc func openSettings(_ call: CAPPluginCall) { | ||
| DispatchQueue.main.async { | ||
| guard let settingsUrl = URL( | ||
| string: UIApplication.openSettingsURLString | ||
| ) else { | ||
| return call.reject("No link to settings available") | ||
| } | ||
| if UIApplication.shared.canOpenURL(settingsUrl) { | ||
| UIApplication.shared.open(settingsUrl, completionHandler: { | ||
| (success) in | ||
| if (success) { | ||
| return call.resolve() | ||
| } else { | ||
| return call.reject("Failed to open settings") | ||
| } | ||
| }) | ||
| } else { | ||
| return call.reject("Cannot open settings") | ||
| } | ||
| } | ||
| } | ||
| public func locationManager( | ||
| _ manager: CLLocationManager, | ||
| didFailWithError error: Error | ||
| ) { | ||
| if let watcher = self.watchers.first( | ||
| where: { $0.locationManager == manager } | ||
| ) { | ||
| if let call = self.bridge?.savedCall(withID: watcher.callbackId) { | ||
| if let clErr = error as? CLError { | ||
| if clErr.code == .locationUnknown { | ||
| // This error is sometimes sent by the manager if | ||
| // it cannot get a fix immediately. | ||
| return | ||
| } else if (clErr.code == .denied) { | ||
| watcher.stop() | ||
| return call.reject( | ||
| "Permission denied.", | ||
| "NOT_AUTHORIZED" | ||
| ) | ||
| } | ||
| } | ||
| return call.reject(error.localizedDescription, nil, error) | ||
| } | ||
| } | ||
| } | ||
| public func locationManager( | ||
| _ manager: CLLocationManager, | ||
| didUpdateLocations locations: [CLLocation] | ||
| ) { | ||
| if let location = locations.last { | ||
| if let watcher = self.watchers.first( | ||
| where: { $0.locationManager == manager } | ||
| ) { | ||
| if watcher.isLocationValid(location) { | ||
| if let call = self.bridge?.savedCall(withID: watcher.callbackId) { | ||
| return call.resolve(formatLocation(location)) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| public func locationManager( | ||
| _ manager: CLLocationManager, | ||
| didChangeAuthorization status: CLAuthorizationStatus | ||
| ) { | ||
| // If this method is called before the user decides on a permission, as | ||
| // it is on iOS 14 when the permissions dialog is presented, we ignore | ||
| // it. | ||
| if status != .notDetermined { | ||
| if let watcher = self.watchers.first( | ||
| where: { $0.locationManager == manager } | ||
| ) { | ||
| return watcher.start() | ||
| } | ||
| } | ||
| } | ||
| } | ||
Sorry, the diff of this file is not supported yet
53236
2.66%19
5.56%270
1.12%