LocationManager gets called multiple times on app startup - swift

So I am dealing with an issue where the LocationManager file that I have, and the didUpdateLocations function, is being called multiple times on application startup and I can't figure out what is causing it.
So I have the following LocationManager:
import Foundation
import CoreLocation
import MapKit
final class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var userLocation: CLLocation?
#Published var defaultRegion: MKCoordinateRegion?
#objc static let getInstance = LocationManager()
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
log.info("\n 🟢: (LocationManager) - Initialize Location Services inside init()")
}
func startUpdatingLocation() {
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func requestLocation() {
locationManager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
DispatchQueue.main.async {
self.userLocation = location
log.info("\n 🟡: (LocationManager) - Setup new location as: \(location)")
self.defaultRegion = MKCoordinateRegion(center: location.coordinate, span: USER_LOCATION_SPAN)
log.info("\n 🟡: (LocationManager) - Setup default region as \(location.coordinate.latitude) and \(location.coordinate.longitude).")
self.locationManager.stopUpdatingLocation()
log.info("\n 🟡: (LocationManager) - Stop updating the location.")
}
}
}
Then I am utilizing it in two separate view files called MapUIView and MapUIView2 and I have the following object in both:
#ObservedObject var locationManager = LocationManager.getInstance
This causes the DispatchQueue.main.async inside didUpdateLocations to run through twice on application start - Is this normal behavior or is it possible to have the didUpdateLocations to run just once, but handle multiple views?

Related

Could not get current Location from CLLocationManager

class LocationManager: NSObject, CLLocationManagerDelegate {
private var onLocation: ((CLLocationCoordinate2D) -> Void)?
private let manager: CLLocationManager
override init() {
manager = CLLocationManager()
super.init()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.delegate = self
manager.startUpdatingLocation()
}
public func getLocation(_ onLocation: ((CLLocationCoordinate2D) -> Void)?) {
self.onLocation = onLocation
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(#function, locations)
guard let currentCoordinate = manager.location?.coordinate else {return}
onLocation?(currentCoordinate)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(#function, error)
}
}
this code is not calling didUpdateLocation or didFailWithError. can anyone tell me what could be the problem here?
LocationManager().getLocation { coordinate in
print(#function, coordinate)
}
this is how i am calling it.
You need to retain the let manager = CLLocationManager() in your class as a property. Otherwise, it will be deallocated at the end of that function and hence none of its delegate methods will be called at all.
UPDATED
Another issue is the following code where you call getLocation. You need to retain LocationManager() in your client class otherwise the LocationManager will be deallocated at the end of that function.
private let locationManager = LocationManager()
locationManager.getLocation { coordinate in
print(#function, coordinate)
}

Onchange of variable in class

I have a class such as:
class LocationViewModel: NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var lastSeenLocation: CLLocation?
#Published var currentPlacemark: CLPlacemark?
#Published var authorizationStatus: CLAuthorizationStatus
private let locationManager: CLLocationManager
override init() {
locationManager = CLLocationManager()
authorizationStatus = locationManager.authorizationStatus
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func requestPermission() {
locationManager.requestAlwaysAuthorization()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
authorizationStatus = manager.authorizationStatus
}
}
I'm trying to check if last seen location = cordinates using this code:
let radius: Double = 5 // miles
let userLocation = CLLocation(latitude: locationViewModel.lastSeenLocation?.coordinate.latitude, longitude: locationViewModel.lastSeenLocation?.coordinate.longitude)
let venueLocation = CLLocation(latitude: 51.500909, longitude: -0.177366)
let distanceInMeters = userLocation.distanceFromLocation(venueLocation)
let distanceInMiles = distanceInMeters * 0.00062137
if distanceInMiles < radius {
// user is near the venue
}
The only problem is, that I don't know how to run that code to check constantly. I was thinking .onChange but couldn't figure out how to test for lastSeenlocation in a class. What can I do?
I found a solution here.
For a quick overview:
//I put this code in my LocationViewModel class
func getUserLocation() {
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
print("latitude: \(location.coordinate.latitude), longitude: \(location.coordinate.longitude)")
}
}
//I put this code in my ContentView()
.onAppear {
locationViewModel.getUserLocation()
}
Then it prints your latitude & longitude every time your location changes.

Checking if location services are enabled. [Swift]

I am using the following observable object to track the users location:
import Foundation
import MapKit
class LocationManager: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
#Published var location: CLLocation? = nil
override init() {
super.init()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
func locationServicesEnabled() -> Bool {
return self.locationManager.locationServicesEnabled()
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.location = location
}
}
However, when I try to compile I get an error next to the return self.locationManager.locationServicesEnabled() statement. The compiler says: Static member 'locationServicesEnabled' cannot be used on instance of type 'CLLocationManager', Replace 'self.locationManager' with 'CLLocationManager'. I do not understand this error because self.locationManager is an instance of CLLocationManager. Please help.
Static means that locationServicesEnabled is a member of the class, not of the object. Use the classname CLLocationManager as suggested.
Seems like the API has changed, earlier it was an instance method, now it is a class method:
https://developer.apple.com/documentation/corelocation/cllocationmanager/1620566-locationservicesenabled
static func configureLocationManager(manager: CLLocationManager, delegate: CLLocationManagerDelegate?) {
manager.allowsBackgroundLocationUpdates = true
manager.delegate = delegate
manager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
switch manager.authorizationStatus {
case .authorizedAlways, .authorizedWhenInUse:
if CLLocationManager.locationServicesEnabled() {
manager.requestLocation()
}
default:
manager.requestWhenInUseAuthorization()
}
}
Here a piece of code that configured location manager and checks whether the app was allowed by user to use location services.
Here is more code that could be useful. This is a file that I created to implement the delegate methods.
https://github.com/aibo-cora/Safe-Bavaria/blob/main/Safe%20Bavaria/View%20Model%20-%20Utility/Find.User.Location.swift

I want to take out the coordinates acquired in the delegate

I wrote a program that uses LocationManagerDelegate to display coordinates in the debug area whenever the current location changes. Got an error when retrieving coordinates
Can not use instance member 'locationManager' within property initializer; property initializers run before 'self' is available
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate{
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setUpLocationManager()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setUpLocationManager() {
locationManager = CLLocationManager()
guard let locationManager = locationManager else {return}
locationManager.requestWhenInUseAuthorization()
let status = CLLocationManager.authorizationStatus()
if status == .authorizedWhenInUse {
locationManager.delegate = self
locationManager.distanceFilter = 10
locationManager.startUpdatingLocation()
printLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) ->Optional<Any> {
let location = locations.first
let latitude = location?.coordinate.latitude
let longitude = location?.coordinate.longitude
let latlong = [latitude, longitude]
return latlong
}
let myLocation = locationManager()
func printLocation() {
print("test\(myLocation)")
}
}
test (Function)
is output
let myLocation = locationManager ()
When you change to
let myLocation = locationManager
Your code contains a few mistakes.
The error occurs because you cannot execute the affected line on the top level of the class.
First of all you must not change signatures of delegate methods. This custom delegate method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) ->Optional<Any> { ...
will never be called.
And apart from that why do you declare the return type as Any? although it's supposed to be [CLLocationCoordinate2D]?
Create the location manager immediately, replace
var locationManager: CLLocationManager!
with
let locationManager = CLLocationManager()
In setUpLocationManager() delete the lines
locationManager = CLLocationManager()
guard let locationManager = locationManager else {return} // this line is completely pointless anyway
printLocation()
The delegate method didUpdateLocations is called periodically and asynchronously. Print the result inside the method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
let latitude = location.coordinate.latitude
let longitude = location.coordinate.longitude
let latlong = [latitude, longitude]
print("test", latlong)
}
Delete
let myLocation = locationManager()
func printLocation() {
print("test\(myLocation)")
}

Can't read variable of singleton object

I've implemented a singleton object for CoreLocation's location manager for an app I'm developing using the class constant method for Swift 1.2 and above explained here.
Although when I try to access the currentLocation variable either directly or using the getter method, I get nil.
What am I missing?
Implementation
import Foundation
import CoreLocation
class LocationService: NSObject, CLLocationManagerDelegate {
static let sharedInstance = LocationService()
var locationManager: CLLocationManager!
var currentLocation: CLLocationCoordinate2D!
var currentDirection: StepDirection!
private override init() {
super.init()
locationManager = CLLocationManager()
locationManager.requestAlwaysAuthorization()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.headingFilter = kCLHeadingFilterNone
locationManager.distanceFilter = 1
}
// MARK: Control Methods
func startUpdatingLocation() {
locationManager.startUpdatingLocation()
print("Location updates are started.")
}
func stopUpdatingLocation() {
locationManager.stopUpdatingLocation()
print("Location updates are stopped.")
}
func startUpdatingHeading() {
locationManager.startUpdatingHeading()
print("Compass updates are started.")
}
func stopUpdatingHeading() {
locationManager.stopUpdatingHeading()
print("Compass updates are stopped.")
}
// MARK: CoreLocation Location Updates
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// If location data can be determined
if let location = locations.last! as CLLocation! {
currentLocation = location.coordinate
// print("Current Location: \(currentLocation)")
NSNotificationCenter.defaultCenter().postNotificationName("LocationUpdate", object: self,
userInfo: ["longitude": currentLocation.longitude,
"latitude": currentLocation.latitude])
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Location Manager: \(error)")
NSNotificationCenter.defaultCenter().postNotificationName("LocationUpdateError", object: self,
userInfo: nil)
}
// MARK: CoreLocation Heading Updates
func locationManager(manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
let trueHeading = newHeading.trueHeading
var declanation = (newHeading.trueHeading - newHeading.magneticHeading)
declanation = 50.0
if (0.0+declanation <= trueHeading) && (trueHeading <= 90.0+declanation) {
currentDirection = StepDirection.Right
} else if (90.0+declanation < trueHeading) && (trueHeading <= 180.0+declanation) {
currentDirection = StepDirection.Down
} else if (180.0+declanation < trueHeading) && (trueHeading <= 270.0+declanation) {
currentDirection = StepDirection.Left
} else if (270.0+declanation < trueHeading) && (trueHeading <= 360.0+declanation) {
currentDirection = StepDirection.Up
}
NSNotificationCenter.defaultCenter().postNotificationName("CompassUpdate", object: self, userInfo: ["currentDirection": currentDirection.rawValue])
}
func locationManagerShouldDisplayHeadingCalibration(manager: CLLocationManager) -> Bool {
return true
}
// MARK: Access Methods
func getCurrentLocation() -> CLLocationCoordinate2D! {
return currentLocation
}
}
Access
I first tried accessing it like the following:
LocationService.sharedInstance.currentLocation or LocationService.sharedInstance.getCurrentLocation
I then assigned the shared instance to a variable thinking that I wasn't preserving the state:
locationService = LocationService.sharedInstance
And then using the access methods or the variable names:
locationService.currentLocation or locationService.getCurrentLocation
You need to call your startUpdating function to get the location manager to start updating the location.
private override init() {
super.init()
locationManager = CLLocationManager()
locationManager.requestAlwaysAuthorization()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.headingFilter = kCLHeadingFilterNone
locationManager.distanceFilter = 1
locationManager.startUpdatingLocation()
}
As you asked in comments, the location manager will continuously attempt to get a user's location until stop updating is called.
Discussion
This method returns immediately. Calling this method causes the location manager to obtain an initial location fix (which may take several seconds) and notify your delegate by calling its locationManager:didUpdateLocations: method. After that, the receiver generates update events primarily when the value in the distanceFilter property is exceeded. Updates may be delivered in other situations though. For example, the receiver may send another notification if the hardware gathers a more accurate location reading.
You can read more here: https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/instm/CLLocationManager/startUpdatingLocation
You can access new location data from the delegate method using the location property of the location manager (which you already do in your delegate method).