I would like to create an Swift func like that:
static func queryLocation(desiredAccuracy: CLLocationAccuracy, completion: #escaping (Result<CLLocation, Error>)->())
This func would be as simple as it can get. As caller, you don't need to hold on to your location manager and you don't need to implement any delegate methods. Just ask for the location and implement the completion handler.
(original source code removed and added as answer, because it actually worked - I just had issues calling it properly)
Sorry everyone. There was a problem with the calling method. The OneTimeLocation class is actually working as expected. (also available as Swift Package: https://github.com/hoereth/CoreLocationUtils/blob/main/Sources/CoreLocationUtils/OneTimeLocation.swift)
import Foundation
import CoreLocation
public class OneTimeLocation : NSObject, CLLocationManagerDelegate {
public enum LocationError : Error {
case denied
case restricted
case timeout
case unknown
}
let manager: CLLocationManager
let completion: (Result<CLLocation, Error>)->()
let timeout: TimeInterval
fileprivate static let instancesQueue = DispatchQueue(label: "OneTimeLocation.instances")
fileprivate static var instances = Set<OneTimeLocation>()
/// Will either find you the current location or produce an error.
/// - Parameters:
/// - desiredAccuracy: see CLLocationManager.desiredAccuracy
/// - timeout: Applies to actual finding a location. Dialogs are presented without timeout.
public static func queryLocation(desiredAccuracy: CLLocationAccuracy, timeout: TimeInterval, completion: #escaping (Result<CLLocation, Error>)->()) {
let oneTimeLocation = OneTimeLocation(desiredAccuracy: desiredAccuracy, completion: completion, timeout: timeout)
oneTimeLocation.manager.delegate = oneTimeLocation
switch CLLocationManager.authorizationStatus() {
case .authorizedAlways, .authorizedWhenInUse:
instancesQueue.sync {
_ = instances.insert(oneTimeLocation)
oneTimeLocation.manager.startUpdatingLocation()
DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
oneTimeLocation.manager.stopUpdatingLocation()
completion(Result.failure(LocationError.timeout))
oneTimeLocation.removeInstance()
}
}
case .notDetermined:
instancesQueue.sync {
_ = instances.insert(oneTimeLocation)
oneTimeLocation.manager.requestWhenInUseAuthorization()
}
case .denied:
completion(Result.failure(LocationError.denied))
case .restricted:
completion(Result.failure(LocationError.restricted))
#unknown default:
completion(Result.failure(LocationError.unknown))
}
}
fileprivate init(desiredAccuracy: CLLocationAccuracy, completion: #escaping (Result<CLLocation, Error>)->(), timeout: TimeInterval) {
self.manager = CLLocationManager()
self.manager.desiredAccuracy = desiredAccuracy
self.completion = completion
self.timeout = timeout
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if (locations.count > 0) {
self.completion(Result.success(locations[0]))
} else {
self.completion(Result.failure(LocationError.unknown))
}
self.manager.stopUpdatingLocation()
self.removeInstance()
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
self.completion(Result.failure(error))
self.manager.stopUpdatingLocation()
self.removeInstance()
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch CLLocationManager.authorizationStatus() {
case .authorizedAlways, .authorizedWhenInUse:
self.manager.startUpdatingLocation()
DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
self.manager.stopUpdatingLocation()
self.completion(Result.failure(LocationError.timeout))
self.removeInstance()
}
case .notDetermined:
break;
case .denied:
completion(Result.failure(LocationError.denied))
self.removeInstance()
case .restricted:
completion(Result.failure(LocationError.restricted))
self.removeInstance()
#unknown default:
completion(Result.failure(LocationError.unknown))
self.removeInstance()
}
}
fileprivate func removeInstance() {
Self.instancesQueue.sync {
_ = OneTimeLocation.instances.remove(self)
}
}
}
Related
In the following code, I'm trying to call a method located inside the LocationManagerViewModel class when the locationManagerDidChangeAuthorization is called from inside the LocationManager class via delegation, but nothing happens when it gets called, I only see the debug message Calling method locationManagerDidChangeAuthorization but the method locationManagerDelegateComunicationMethod inside the LocationManagerViewModel never gets called.
Any idea what could be wrong?
Location Manger Class; Sender
protocol LocationManagerDelegate {
func locationManagerDelegateComunicationMethod()
}
final class LocationManager: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
var delegate: LocationManagerDelegate?
var hasFoundOnePlacemark:Bool = false
func checkIfLocationServicesIsEnabled(){
DispatchQueue.global().async {
if CLLocationManager.locationServicesEnabled(){
self.locationManager.delegate = self
self.checkLocationAuthorization()
}else{
print("You have Location Services DESABLED!")
}
}
}
private func checkLocationAuthorization(){
switch locationManager.authorizationStatus{
case .notDetermined:
print("notDetermined")
case .restricted:
print("restricted")
case .denied:
print("denied")
case .authorizedWhenInUse, .authorizedAlways:
print("authorizedWhenInUse")
default:
break
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
print("Calling method locationManagerDidChangeAuthorization")
var delegate: LocationManagerDelegate? = LocationManagerViewModel()// I tried with and without this line
delegate?.locationManagerDelegateComunicationMethod()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
hasFoundOnePlacemark = false
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)-> Void in
if error != nil {
self.locationManager.stopUpdatingLocation()
}
if placemarks!.count > 0 {
if !self.hasFoundOnePlacemark{
self.hasFoundOnePlacemark = true
self.locationManager.stopUpdatingLocation()
}else{
self.noPlacesFoundAlertView()
}
})
}
}
LocationManagerViewModel Class; Receiver
class LocationManagerViewModel: ObservableObject, LocationManagerDelegate{
// some #Published properties here
private var locationManager = LocationManager()
func locationManagerDelegateComunicationMethod() {
print("Received message from the LocationManager class!")
// do something...
}
}
Thanks
The delegate is never assigned in LocationManagerViewModel. You probably need to assign it in an init:
class LocationManagerViewModel: ObservableObject, LocationManagerDelegate{
// some #Published properties here
private var locationManager = LocationManager()
init(){
locationManager.delegate = self
}
func locationManagerDelegateComunicationMethod() {
print("Received message from the LocationManager class!")
// do something...
}
}
I Liked the concept of Protocol Oriented Programming and I am trying to start writing protocols by Protocol Extensions in swift.
I am trying to bring device location using Protocol Extensions.
However I am facing problems on creating that as the following:
import Foundation
import CoreLocation
protocol Locator:CLLocationManagerDelegate {
var locationManager:CLLocationManager!
{ get set }
var locationHandler: ((CLLocation)->())?
{ get set }
func getLocation(completionHandler:#escaping (CLLocation)->())
}
extension Locator {
private var _locationManager:CLLocationManager {
get {return self.locationManager} set {self.locationManager = newValue}
}
private var _locationHandler:((CLLocation)->())? {
get {return self.locationHandler} set {self.locationHandler = newValue}
}
func getLocation(completionHandler:#escaping (CLLocation)->()) {
self.locationManager = CLLocationManager()
self.locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
let status = CLLocationManager.authorizationStatus()
switch status {
case .notDetermined:
self.locationManager.requestAlwaysAuthorization()
return
case .denied, .restricted:
return
case .authorizedAlways, .authorizedWhenInUse:
self.locationManager.startUpdatingLocation()
#unknown default:
break
}
self.locationHandler = completionHandler
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse || status == .authorizedAlways {
self.locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("Hey this is a location")
if let currentLocation = locations.last {
self._locationHandler?(currentLocation)
}
}
}
However the completion handler is not working properly.
what is my problem and How can I proceed creating this in Protocols concept.
didUpdateLocations is not firing
- CLLocationManagerDelegate was implemented correctly with the viewcontroller as a delegate
import Foundation
import UIKit
import CoreLocation
protocol LocationServiceDelegate {
func tracingLocation(currentLocation: CLLocation)
func tracingLocationDidFailWithError(error: NSError)
}
class LocationService: NSObject, CLLocationManagerDelegate {
static var sharedInstance = LocationService()
var locationManager: CLLocationManager?
var currentLocation: CLLocation?
var delegate: LocationServiceDelegate?
var paymentVC: PaymentViewController?
override init() {
super.init()
self.locationManager = CLLocationManager()
guard let locationManager = self.locationManager else {
return
}
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestAlwaysAuthorization()
}
locationManager.distanceFilter = 200
}
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager!.startUpdatingLocation()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager?.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.currentLocation = location
updateLocation(currentLocation: location)
}
private func locationManager(manager: CLLocationManager, didFailWithError error: Error) {
updateLocationDidFailWithError(error: error as NSError)
}
private func updateLocation(currentLocation: CLLocation){
guard let delegate = self.delegate else {
return
}
delegate.tracingLocation(currentLocation: currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.tracingLocationDidFailWithError(error: error)
}
}
this is the extension of the viewcontroller where I implement to custom protocole for corelocation tracking
I call startUpdatingLocations() in viewDidLoad
extension PaymentViewController: LocationServiceDelegate,CLLocationManagerDelegate {
func tracingLocation(currentLocation: CLLocation) {
locationService.currentLocation = currentLocation
}
func tracingLocationDidFailWithError(error: NSError) {
print("Error message: \(error.localizedDescription)")
}
func startUpdatingLocations() {
locationService.locationManager?.delegate = self
locationService.delegate = self
locationService.startUpdatingLocation()
}
func stopUpdatingLocations() {
LocationService.sharedInstance.stopUpdatingLocation()
}
}
the corelocation tracking is not firing in the simulator. However this is enabled.
enter image description here
unfortunately, I have no way to test with a device right now
The only way I know of to simulate core location tracking on a simulator is by choosing one of the location options available in debug tab of the simulator.
I am trying to use the below LocationSingleton Class from this blog in my project. I like the simplicity of its usage.
You start updating location by simply calling:
LocationSingleton.sharedInstance.startUpdatingLocation()
Get the last location by simply calling:
LocationSingleton.sharedInstance.lastLocation
My intention is to start location services, get the last location so that I can then fetch users from Firebase using the location returned.
The issue is that if I call lastLocation straight after startUpdatingLocation it returns nil.
After some debugging I've found the reason is because location services are slow to start on the device and therefore when lastLocation is called the devices hasn't acquired the location yet. I would like to execute the next command as soon as the lastLocation has been recorded. How can I achieve that?
I would like to understand how the Protocol is being used?
import UIKit
import CoreLocation
protocol LocationServiceDelegate {
func locationDidUpdateToLocation(currentLocation: CLLocation)
func locationUpdateDidFailWithError(error: NSError)
}
class LocationSingleton: NSObject,CLLocationManagerDelegate {
var locationManager: CLLocationManager?
var lastLocation: CLLocation?
var delegate: LocationServiceDelegate?
static let sharedInstance:LocationSingleton = {
let instance = LocationSingleton()
return instance
}()
override init() {
super.init()
self.locationManager = CLLocationManager()
guard let locationManagers=self.locationManager else {
return
}
if CLLocationManager.authorizationStatus() == .notDetermined {
//locationManagers.requestAlwaysAuthorization()
locationManagers.requestWhenInUseAuthorization()
}
if #available(iOS 9.0, *) {
// locationManagers.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
}
locationManagers.desiredAccuracy = kCLLocationAccuracyBest
locationManagers.pausesLocationUpdatesAutomatically = false
locationManagers.distanceFilter = 0.1
locationManagers.delegate = self
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.lastLocation = location
updateLocation(currentLocation: location)
}
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager?.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse:
locationManager?.startUpdatingLocation()
break
case .authorizedAlways:
locationManager?.startUpdatingLocation()
break
case .restricted:
// restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// user denied your app access to Location Services, but can grant access from Settings.app
break
default:
break
}
}
// Private function
private func updateLocation(currentLocation: CLLocation){
guard let delegate = self.delegate else {
return
}
delegate.locationDidUpdateToLocation(currentLocation: currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.locationUpdateDidFailWithError(error: error)
}
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager?.startUpdatingLocation()
// self.locationManager?.startMonitoringSignificantLocationChanges()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager?.stopUpdatingLocation()
}
func startMonitoringSignificantLocationChanges() {
self.locationManager?.startMonitoringSignificantLocationChanges()
}
}
The location manager works asynchronously and provides delegate methods to get the result.
In your class adopt LocationServiceDelegate, implement the delegate methods and set the delegate for example in viewDidLoad
func locationDidUpdateToLocation(currentLocation: CLLocation)
{
print(LocationSingleton.sharedInstance.lastLocation)
}
func locationUpdateDidFailWithError(error: NSError)
{
print(error)
}
func viewDidLoad()
super viewDidLoad()
let locationSingleton = LocationSingleton.sharedInstance
locationSingleton.delegate = self
locationSingleton.startUpdatingLocation()
}
When a location is detected, one of the delegate methods is called
I'm trying to implement a dedicated class to scan for a specific iBeacon (in foreground and in background) but the function didRangeBeacons is never called.
I also set the parameter NSLocationAlwaysUsageDescription in my info.plist.
here is the class I developed:
class iBeacon: NSObject, UIApplicationDelegate, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
var beaconRegion:CLBeaconRegion!
override init(){
super.init()
print("init...")
let uuidString = "B737D0E7-AF53-9B83-E5D2-922140A91234"
let beaconIdentifier = "nbuit-06B908"
let beaconUUID:NSUUID = NSUUID(UUIDString: uuidString)!
beaconRegion = CLBeaconRegion(proximityUUID: beaconUUID,
identifier: beaconIdentifier)
beaconRegion.notifyOnEntry = true
locationManager = CLLocationManager()
locationManager.delegate = self
if(locationManager!.respondsToSelector("requestAlwaysAuthorization")) {
locationManager!.requestAlwaysAuthorization()
}
locationManager!.pausesLocationUpdatesAutomatically = false
locationManager!.startMonitoringForRegion(beaconRegion)
locationManager!.startRangingBeaconsInRegion(beaconRegion)
locationManager!.startUpdatingLocation()
}
func sendLocalNotificationWithMessage(message: String!) {
let notification:UILocalNotification = UILocalNotification()
notification.alertBody = message
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!){
locationManager.requestStateForRegion(region)
}
func locationManager(manager: CLLocationManager!, didDetermineState state: CLRegionState, forRegion region: CLRegion!) {
if state == CLRegionState.Inside {
locationManager.startRangingBeaconsInRegion(beaconRegion)
}
else {
locationManager.stopRangingBeaconsInRegion(beaconRegion)
}
}
func locationManager(manager: CLLocationManager,
didRangeBeacons beacons: [CLBeacon],
inRegion region: CLBeaconRegion) {
print("didRangeBeacons");
var message:String = ""
if(beacons.count > 0) {
let nearestBeacon:CLBeacon = beacons[0] as! CLBeacon
switch nearestBeacon.proximity {
case CLProximity.Far:
message = "You are far away from the beacon"
case CLProximity.Near:
message = "You are near the beacon"
case CLProximity.Immediate:
message = "You are in the immediate proximity of the beacon"
case CLProximity.Unknown:
return
}
} else {
message = "No beacons are nearby"
}
print("%#", message)
sendLocalNotificationWithMessage(message)
}
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
print("Beacon in range")
}
func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
print("No beacons in range")
}
func locationManager(manager: CLLocationManager!, monitoringDidFailForRegion region: CLRegion!, withError error: NSError!) {
print("Failed monitoring region: \(error.description)")
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
print("Location manager failed: \(error.description)")
}
}
I'm not able to find the reason why my iBeacon is not detected. I also checked for the UUID and name in a BLE scanner app and it seems to be correct.
If you have any idea to help me that would be great.
EDIT:
I Finally found the reason why the LocationManager was never started, I was instantiating my class like this:
let qualityOfServiceClass = DISPATCH_QUEUE_PRIORITY_DEFAULT
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
print("Running beacon detection in the background queue")
beaconSharedInstance
})
I was expecting to get this instance running in background, but by simply calling my instance like this
beaconSharedInstance
it starts and scans for regions correctly. but as soon as my app is inactive, I have the following log:
Ending background task
and my app stops scanning. I set the following parameters
locationManager.allowsBackgroundLocationUpdates = true
and also set Background Mode capability of my project.
any idea why my instance is stopped when the app is in background ?