Swift: Get Current User Coordinates and Store them into a Variable - swift

I am currently trying to get the current coordinates of the user and ultimately store those values into variables.
I have created the following class to define the users current location and set up functions to pull data.
import Foundation
import CoreLocation
class MyCurrentCoordinate: NSObject {
private var currentLocation: CLLocation!
var myLatitude = 0.0
var myLongitude = 0.0
var myAltitude = 0.0
override init() {
super.init()
}
func getLat() {
myLatitude = currentLocation.coordinate.latitude
}
func getLong() {
myLongitude = currentLocation.coordinate.longitude
}
func getAlt() {
myAltitude = currentLocation.altitude
}
}
This does not show any errors. However, when I go to call any function (getLat, getLong, or getAlt) to pull a piece of the users location data, the app crashes due the value being nil. Does anyone have any insight as to why the actual user lat, long, or altitude is not being passed?
I have the location permission and info.plist updated to allow the user to give location tracking permission.

import Foundation
import CoreLocation
import UIKit
public protocol LocalizationHelperDelegate: class {
func didUpdateLocation(_ sender: CLLocation)
}
public class LocalizationHelper: NSObject {
public weak var delegate: LocalizationHelperDelegate?
public static var shared = LocalizationHelper()
private lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyBest
return locationManager
}()
private var currentLocation: CLLocationCoordinate2D?
public func startUpdatingLocation() {
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
public func stopUpdatingLocation() {
locationManager.stopUpdatingLocation()
}
public func getCurrentLocation() -> CLLocationCoordinate2D? {
return currentLocation
}
public func getLat() -> Double{
return currentLocation?.latitude ?? 0.0
}
public func getLon() -> Double{
return currentLocation?.longitude ?? 0.0
}
}
extension LocalizationHelper: CLLocationManagerDelegate {
public func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]){
guard let location = locations.first else { return }
currentLocation = location.coordinate
print("[Update location at - \(Date())] with - lat: \(currentLocation!.latitude), lng: \(currentLocation!.longitude)")
delegate?.didUpdateLocation(location)
}
}
How to use
LocalizationHelper.shared.Start()
...
let lat = LocalizationHelper.shared.getLat()
let lon = LocalizationHelper.shared.getLon()
...
LocalizationHelper.shared.Stop()

You can use CLLocationManager
Add app capability, you can open your info.plist like source code and add:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>App requires allways tracking</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>App requires background tracking</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>App requires tracking when be in use</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
<string>remote-notification</string>
</array>
Ask for authorization like locationManager.requestAlwaysAuthorization() and manage if have the correct access... CLLocationManager.authorizationStatus() == .authorizedAlways
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestAlwaysAuthorization()
if CLLocationManager.authorizationStatus() == .authorizedAlways {
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
locationManager.allowsBackgroundLocationUpdates = false
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
}
}
extension ViewController: CLLocationManagerDelegate {
public func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]){
guard let location = locations.first else { return }
print(location)
}
public func locationManager(_: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
print("Autorization status did change \(status)")
case .authorizedWhenInUse:
print("Autorization status did change \(status)")
case .authorizedAlways:
print("Autorization status did change \(status)")
case .restricted:
print("Autorization status did change \(status)")
case .denied:
print("Autorization status did change \(status)")
#unknown default:
fatalError()
}
}
}
Don't forget to stop it in some place locationManager.stopUpdatingLocation()

Related

Message sent to other class via Delegation when the locationManagerDidChangeAuthorization method is called not received

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...
}
}

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

CoreLocation enabled in simulator

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.

Why is this Singleton Location Manager class returning a nil Location?

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

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).