LocationManager verify if location is valid? - swift

I am making an app where I must know if the location is valid, not cached, and current.
It's an app that displays what are close to you (with database and stuff)
Every 10 seconds I fetch data from the net and display the new data in a tableView.
Let's say I'm near a shop, and the app displays "You are close to the shop"
I close the app, go home and when I open the app It displays still "You are close to the shop" even though I requested the location..
It is because it returns a cached value back first..
Okay, I'm not that dumb, so I fixed it:
(I save the valid locations into the userLocation object)
So when user closes the app and returns back, it checks if the location is too old.. if so, then the userLocation object gets cleared, and locationManager?.requestLocation() gets called
But still, in the didUpdateLocations functions it checks if the location is too old, and if NOT, aka it is new, then I do fetch new data from the net
// Location Functions
func setupLocationManager(){
let authorizationStatus = CLLocationManager.authorizationStatus()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestAlwaysAuthorization()
locationManager?.startUpdatingLocation() // Log only significant location changes
locationManager?.pausesLocationUpdatesAutomatically = true // If user is not moving, don't update location
locationManager?.desiredAccuracy = kCLLocationAccuracyNearestTenMeters // Ten meter accuracy
locationManager?.distanceFilter = 20 // Update location only if 20m location change
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
currentLocation = locations.last
// Check if location is not too old.. (aka cached)
let locationIsValid:Bool = Date().timeIntervalSince(currentLocation!.timestamp) < 11
if locationIsValid
{
currentLocationValue = currentLocation?.coordinate
// If first loc after opening / returning to app, then fetch data
if (userLocation.latitude == nil && userLocation.longitude == nil){
userLocation.location = currentLocation
userLocation.latitude = currentLocationValue?.latitude
userLocation.longitude = currentLocationValue?.longitude
mainFetchData()
}
userLocation.location = currentLocation
userLocation.latitude = currentLocationValue?.latitude
userLocation.longitude = currentLocationValue?.longitude
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// Error so remove global variable user location
userLocation.location = nil
userLocation.latitude = nil
userLocation.longitude = nil
}
// THIS GETS CALLED EVERYTIME applicationDidBecomeActive !
#objc func notification_validateCachedLocation(){
if userLocation.location != nil {
let lastCachedLocationIsInvalid:Bool = Date().timeIntervalSince(userLocation.location!.timestamp) > 10
if lastCachedLocationIsInvalid
{
userLocation.location = nil
userLocation.latitude = nil
userLocation.longitude = nil
locationManager?.requestLocation()
}
} else {
locationManager?.requestLocation()
}
}
But still there's a problem:
Let's say You're just home... you open the app, and it saves your location. You then close it.
Okay, so after siting for like 20 minutes, you return back and open the app again..
then the LocationManager loads the locations and because you haven't moved since, it doesn't get updated. And because of that, it will be older than 10 seconds, it will be just a cached location and therefore my app will not fetch data :C

Seems to me you should stop using pausesLocationUpdatesAutomatically and just manually pause it (stop locating) at times that suit you.

Related

No stable location updates are received in CLLocationManager didUpdateLocations delegate. I don't know what is wrong

I am trying to get location updates with locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]). The locations are received. But when I am debugging the app in device, I have recorded some weird events.
One was that the phone was never removed from place while debugging but it recorded different locations over a period of 15 minutes and the distance summed up to around 50meters.
Another was that when the first time the delegate was fired, it provided the correct current location, but soon it moved some 800 meters away and then restored to correct location after some time.
What I am trying to do in this app is to calculate travelled distance over time. But since I am not getting correct data, the distance I am getting is wrong.
The locationmanager has been used in this way
locationManager = CLLocationManager()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = 1
locationManager.activityType = .automotiveNavigation
locationManager.pausesLocationUpdatesAutomatically = true
and the lastLocation from delegate is saved and used
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let bestLocation = locations.last {
guard -(bestLocation.timestamp.timeIntervalSinceNow) < 5 || bestLocation.horizontalAccuracy > 0 else {
return
}
self.lastLocation = bestLocation
}
}
I am saving current location data and comparing it with the next one received every one second to calculate the distance.
So how do I fix this incorrect received location and calculate distance properly. My approach of doing this may be wrong, so please suggest me a better way of handling this.

Launch Location Updates at specific time in background - Swift (watchOS)

I've developing an indipendent WatchOS app whose aim is identifying when an user leaves a specific area, sending consequentially a notification. In order to do that, the application heavily relies on background location updates.
So far, app is working fine. It fetches position based on distanceFilter property of CLLocationManager. The problem is battery. The approach I followed keep background location updates in execution, even though they're fetched only when a specific distance is "covered".
My idea then was to disable location update when the area is left by the user, and also disable this service during night hours. However, I'm facing serious problem with this type of approach.
My main problem is that disabling location update while in background does not allow me to resume it. I tried doing this with:
A Timer.
scheduleBackgroundRefresh(withPreferredDate:userInfo:scheduledCompletion:) method, calling startLocationUpdates() in the delegate
Nothing seems to work. My question is:
There is a way for resume background location updates if it was previously disabled?
Thank you in advance.
UPDATE n.2: I've tried to execute location updates with WKApplicationRefreshBackgroundTask but it just ignore requestLocation() function (suggested by #RomuloBM)
//In extension delegate handle() function
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Be sure to complete the background ta
LocMng = LocationManager() // I even tried to create a new element!
LocMng.LocMng.requestLocation()// it is just ignored
backgroundTask.setTaskCompletedWithSnapshot(false)
I call a background task with this function in my LocationManager:
//In didUpdateLocation
if background {
WKExtension.shared().scheduleBackgroundRefresh(withPreferredDate: Date(timeIntervalSinceNow: 30), userInfo: nil){ _ in
print("Done")
self.background = false
self.LocMng.stopUpdatingLocation()
}
}
For reference, here is my LocationManager class:
enum ScanningMode {
case Precise
case Normal
}
class LocationManager : NSObject, CLLocationManagerDelegate, UNUserNotificationCenterDelegate, ObservableObject {
let LocMng = CLLocationManager()
let NotMng = UNUserNotificationCenter.current()
var modeOfScanning: ScanningMode!
var region: CLCircularRegion!
var previousLocation: CLLocation!
// variables for position...
override init() {
super.init()
// stuff for my app...
modeOfScanning = .Precise
setupManager()
setupNotification()
startLocalization()
}
private func startLocalization(){
switch modeOfScanning!{
case ScanningMode.Precise:
LocMng.desiredAccuracy = kCLLocationAccuracyBest
LocMng.distanceFilter = 15
case ScanningMode.Normal:
LocMng.desiredAccuracy = kCLLocationAccuracyHundredMeters
LocMng.distanceFilter = 80
}
LocMng.startUpdatingLocation()
}
private func setupManager(){
LocMng.requestAlwaysAuthorization()
LocMng.delegate = self
LocMng.desiredAccuracy = kCLLocationAccuracyBest
}
private func setupNotification(){
NotMng.delegate = self
NotMng.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("NotificationCenter Authorization Granted!")
}
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.authorizedAlways{
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
LocMng.allowsBackgroundLocationUpdates = true
// For the sake of clarity, I will cut out this chunk of code and
// just showing how I execute my action based on the result of location
// This is just an example
actualLocation = locations[length-1]
//find if in a forget
if previousLocation != nil{
if !region.contains(actualLocation!.coordinate) && region.contains(previousLocation!.coordinate){
//Schedule notification
LocMng.stopUpdatingLocation() // <- this does not allow me to resume
}
}
previousLocation = actualLocation
}
}

CLLocationManager is slow getting location, Swift

Im creating this app, and it needs to get the users location - its all working properly, the thing is, that the time from accepting the use of location services, to getting the actual location takes like 5 seconds - is this normal?
I've used other apps, where it goes much faster..
Here's what my code looks like:
override func viewDidLoad() {
super.viewDidLoad()
// Ask for Location-Authorisation from the User.
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.requestLocation()
}
mapView.delegate = self
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue: CLLocationCoordinate2D = manager.location!.coordinate
let initialLocation = CLLocation(latitude: locValue.latitude, longitude: locValue.longitude)
self.centerMapOnLocation(initialLocation)
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("could not get location")
}
But the time from the application gets the location to put into the centerMapOnLocation-function, seems just to be quite long. What is to be expected, when getting a users location? I'm testing on a wifi connection, so I know its not because the internet is slow, or its a bad connection...
Anyone have an idea? :)
Best regards!
Try setting the accuracy and use locationManager.startUpdatingLocation(). I do that, and get answer within a second (on the device).
From the documentation of requestLocation():
This method returns immediately. Calling it causes the location manager to obtain a location fix (which may take several seconds) and call the delegate’s locationManager(_:didUpdateLocations:) method with the result.
Source
So basically, everything is fine with your code, it's just how the framework is built.
When initializing your location manager, add startUpdatingLocation():
let manager = CLLocationManager()
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.requestLocation()
manager.startUpdatingLocation()
Without startUpdatingLocation() geo-location takes about 5 seconds, with it, the request executes nearly immediately.
If you don't want to delay the app's launch for the location manager, consider deploying two location managers (in the app delegate), tasking one with generating a location quickly and the other with generating a location accurately:
fastLoc.delegate = self
fastLoc.desiredAccuracy = kCLLocationAccuracyThreeKilometers
fastLoc.requestWhenInUseAuthorization()
fastLoc.startUpdatingLocation()
bestLoc.delegate = self
bestLoc.desiredAccuracy = kCLLocationAccuracyBest
bestLoc.requestWhenInUseAuthorization()
bestLoc.requestLocation()
The combination of 3 km accuracy with startUpdatingLocation() should return a location almost instantly, almost always before the root view controller is even ready to go. bestLoc manager is likely to return a location well after the user has launched the app but it will be very accurate.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
switch manager {
case fastLoc:
fastLoc.stopUpdatingLocation()
deviceLocation = locations.last! // set property with fast loc
case bestLoc:
deviceLocation = locations.last! // overwrite property with best loc
default:
break
}
}

How can I constantly update tableview per 'location changes'?

Alright, so I have a tableview that, when the view is loaded, it's populated with Parse objects that have geopoints within a set range of user's current location. This works here:
let query: PFQuery = PFQuery(className: "Events")
query.whereKey("location", nearGeoPoint: myGeoPoint, withinMiles: range)
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error) in
if error == nil {
print(objects)
for object in objects! {
self.names.append(object["Name"] as! String)
self.descriptions.append(object["description"] as! String)
}
self.tableView.reloadData()
} else {
print(error)
}
}
}
The issue with this is I need to either have this table reloadData constantly OR just whenever the user's location changes which, I predict, will happen pretty frequently. Because it needs to display the items that are within range, I can't have the user drive somewhere and the table still display the items from the last location.
For other parts of the app I have just have the table refresh when a certain button is clicked, however I need to know how to properly update this table ALWAYS or whenever the user's location changes. I have attempted to use a timer set to a fraction of a second, but that caused issues and didn't seem to be the the correct way.
How can I do this? Regarding always updating the table, I have tried
override func viewDidAppear(animated: Bool) {
self.tableView.reloadData()
}
But that does nothing. Looked at loadObjects() also, but had errors. What is the best way to achieve this?
EDITS:
if (CLLocationManager.locationServicesEnabled())
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
//locationManager.startUpdatingLocation() //so not always updating
locationManager.startMonitoringSignificantLocationChanges()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// locations contains an array of recent locations, but this app only cares about the most recent
// which is also "manager.location"
myLoc = PFGeoPoint(location: manager.location)
print("significant change - \(myLoc)")
tableView.reloadData()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError!) {
print("failed")
}
Set the distanceFilter property to your location manager.
locationManager.distanceFilter = 50
Now it will only update the user's location if it has changed by 50 or more meters.

Trouble getting one-time user location in Swift

I am trying to get the user's location at the time of a button press, however I am having trouble setting up the location code. I've been looking between various answers on Stackoverflow but I can't get anything to work for me.
class MyViewController: UIViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedAlways || status == .AuthorizedWhenInUse {
let latitude = self.locationManager.location?.coordinate.latitude
let longitude = self.locationManager.location?.coordinate.longitude
print("coordinates:")
print(latitude)
print(longitude)
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locationValue : CLLocationCoordinate2D = manager.location!.coordinate
print("locations = \(locationValue.latitude) \(locationValue.longitude)")
}
However I just get nils from the prints in didChangeAuthorizationStatus and nothing ever prints from didUpdateLocations. Im also really confused about where to put startUpdatingLocation(). Some answers place it directly in viewDidLoad as I have here, others place it in didChangeAuthorizationStatus, and I saw another place it inside an if CLLocationManager.locationServicesEnabled() in didLoad. What is the correct way to do this? And also what have I done wrong that I'm not getting any values printing? I'm running on the emulator, have location turned on and allowed permissions in the pop-up, and have the emulator set to a lat/long custom location, and I have added the values in pList.
edit: even tried copy/pasting the code from this to get user's location only once but it returns (kCLErrorDomain error 0.). I added a 'requestWhenInUseAuthorization()` to their code but it still fails.
get nils from the prints in didChangeAuthorizationStatus
Because we don't have any locations yet. That's the wrong place to ask for that information. All that happened is that we are now authorized.
nothing ever prints from didUpdateLocations
Because you're running on the simulator, perhaps. Try running on a device.
And, if your goal is just to get the location and stop, don't use startUpdatingLocation; call requestLocation. That's what it's for.