I am developing a screen that will need to update location every 10 minutes using a timer. Other than that It only needs to update location at first load and when the view appears to the user again. It should stop monitoring once the the user goes to another view.
I have a code that is supposed to do this, but the issue is that the didUpdateLocations method is not called at any point. Also the map does not show the current location (I use simulated locations).
I have correctly set up the permissions and the app worked fine when it was setup to just show the location. I need to do this to reduce battery consumption.
Here is my related code:
In viewDidLoad:
if #available(iOS 8.0, *) {
self.locationManager.requestAlwaysAuthorization()
}
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.distanceFilter = 1000
self.locationManager.activityType = CLActivityType.automotiveNavigation
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.pausesLocationUpdatesAutomatically = false
self.locationManager.startUpdatingLocation()
self.map.showsUserLocation = true
In viewWillAppear:
self.map.showsUserLocation = true
self.locationManager.startUpdatingLocation()
In viewWillDisappear:
self.map.showsUserLocation = false
self.locationManager.stopUpdatingLocation()
In didUpdateLocations: (at last line)
self.locationManager.stopUpdatingLocation()
Timer Function: (this gets called fine)
Timer.scheduledTimer(timeInterval: 600.0, target: self, selector: #selector(HomePageViewController.updateLocationFromTimer), userInfo: nil, repeats: true)
#objc func updateLocationFromTimer()
{
self.locationManager.startUpdatingLocation()
}
I also tried to catch any error with the following code:
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
but it did not get called.
I would love to know why the location is not being updated and why the map is not showing the location. Please help.
Make sure you assign the delegate:
self.locationManager.delegate = self
I did not work for me either and discovered that the place at which you set the delegate impacts this.
E.g. this did not work:
var locationManager = CLLocationManager() {
didSet {
locationManager.delegate = self
}
}
Setting it at a later moment did work as expected. Not sure why to be honest, but maybe this helps someone.
Related
I've had an issue for like 3-4 months. I've tried everything you can ever imagine to get this to work, but I really can't. Now I'm looking for your help to fix this issue.
I've an application, when you press a start button it should get locations. (Works perfectly fine when ur on the application.)
But once you leave the application, (not killing the process) and goes to the background. The polyline is not drawing like it should. It pauses or something.
I need someone either who can help me here, or create a chatroom with me so we can discuss and I will send the rest of the code.
Here is parts of it, which I think is the most important.
Inside the viewDidLoad
let app = UIApplication.shared
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(notification:)), name: UIApplication.willResignActiveNotification, object: app)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive(notification:)), name: UIApplication.didBecomeActiveNotification, object: app)
-
#objc func applicationWillResignActive(notification: NSNotification)
{
start = CFAbsoluteTimeGetCurrent()
print("Background entered")
startReceivingSignificantLocationChanges()
}
#objc func didBecomeActive(notification: NSNotification)
{
let elapsed = CFAbsoluteTimeGetCurrent() - start
counter = counter + Int(elapsed)
print("Returned to application")
locationManager.stopMonitoringSignificantLocationChanges()
}
< Inside the start button.
//Checking userpermission to allow map and current location
if (CLLocationManager.locationServicesEnabled())
{
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.showsBackgroundLocationIndicator = true
//Retrieve current position
if let userLocation = locationManager.location?.coordinate
{
//Zooming in to current position
let viewRegion = MKCoordinateRegion(center: userLocation, latitudinalMeters: 200, longitudinalMeters: 200)
mapView.setRegion(viewRegion, animated: false)
//Creating a start annotation
if locations.isEmpty
{
let annotation = MKPointAnnotation()
annotation.title = "Start"
annotation.coordinate = userLocation
mapView.addAnnotation(annotation)
}
self.locations.append(userLocation)
print(self.locations, "First")
//Starts the walk-timer, with interval: 1 second
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
//Sending to update
update()
}
}
< Background worker
func startReceivingSignificantLocationChanges()
{
let authorizationStatus = CLLocationManager.authorizationStatus()
if authorizationStatus != .authorizedAlways
{
return
}
if !CLLocationManager.significantLocationChangeMonitoringAvailable()
{
// The service is not available.
return
}
else
{
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 100.0 //100.0 meters
locationManager.activityType = .fitness
locationManager.allowsBackgroundLocationUpdates = true
locationManager.delegate = self
locationManager.startMonitoringSignificantLocationChanges()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations
locations: [CLLocation])
{
let lastLocation = locations.last!
self.locations.append(lastLocation.coordinate)
print("Locations retrieved from background: ", self.locations)
}
There is a lot more I've to show you. But unfortunately it would be way too much...
Please enable the Background Modes from the capabilities of the project and enable the 'Location updates'. After enabling this, the only configuration to get the updates in the background(not in killed state) is to set'allowsBackgroundLocationUpdates' to true(which you have done already).
Here the significant location changes are only needed when you want to get the location when the application is killed by the user. This significant location change will launch the application in background and read the location of the device. For more information on getting location in the background follow :
https://developer.apple.com/documentation/corelocation/cllocationmanager/1620568-allowsbackgroundlocationupdates
For significant location changes while the application is in killed state, follow below link. This is in objective C but it can be easily done in swift also.
http://mobileoop.com/getting-location-updates-for-ios-7-and-8-when-the-app-is-killedterminatedsuspended
Hope this helps.
I have a simple goal of my app that is get the current coordinate, and use them to add an annotation on the mapview.
I have been tried lots of solution from google results, but its still not working....
The debug area never shows "locationManager did UpdateLocation", the message what I print in function....
It's seems like the app never run "did UpdateLocation" function, even startUpdatingLocation() has been called?
Add location privacy string in info.plist : Done.
Turn on the GPS on my Mac Pro : Done.
Xcode version : 10.1
MacOS : 10.13.6 (High Sierra)
let cloaction = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
MapView.delegate = self
MapView.showsScale = true
MapView.showsPointsOfInterest = true
MapView.showsUserLocation = true
cloaction.requestAlwaysAuthorization()
cloaction.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
print("IN")
cloaction.delegate = self
cloaction.desiredAccuracy = kCLLocationAccuracyBest
cloaction.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("locationManager did UpdateLocation")
let location = CLLocationCoordinate2D(latitude: (locations.first?.coordinate.latitude)!, longitude: (locations.first?.coordinate.longitude)!)
currentLat = (locations.first?.coordinate.latitude)!
currentLon = (locations.first?.coordinate.longitude)!
let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
MapView.setRegion(MKCoordinateRegion(center: CLLocationCoordinate2DMake(currentLat,currentLon), span: span), animated: true)
MapView.showsUserLocation = true
print(locations.first?.coordinate.latitude)
print(locations.first?.coordinate.longitude)
}
Actually the reason is very simple: You call the didUpdateLocation mehthod wich is only called when you change your location. Your Mac is on certain place and dont move so thats why it is not working.
Have you import CoreLocation?
Start with making a variable let myLocation = CLLocation()
Instead of have so much in viewDidLoad you can make a function and call the mLocation in viewDidLoad instead :
func mLocation(){
cloaction.delegate = self
cloaction.desiredAccuracy = kCLLocationAccuracyBest
cloaction.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled(){
cloaction.startUpdatingLocation()
}
}
And thats all you need for the clocation
LocationManager could also be updated
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]){
let newLocation = locations[0]
print("\(myLocation.coordinate.latitude)")
print("\(myLocation.coordinate.longitude)")
}
Yes! finally... thank your answering, it's really need to run on device, thanks Kosuke Ogawa's suggestion, and every one's guide, I am a new to learn swift, and first time ask question here, it's fun, thank you every one.
(But I don't know how to accept a answer if the answer is a comment? Someone teach me how do that?)
When the app is in the foreground, the location updates every second and works perfectly, which is what I want. But when the app is in the background (or in suspended state, I'm not sure), I do not get any local notifications(Ive tested the local notifications on the phone alone, so this isn't the problem). I use local notifications to tell me if the location has been updated, which is how I know the location isn't being updated.
I've set up the background capabilities and info.plist i.e "Privacy - Location When in Use Usage Description" etc. And I've added the below commands.
All my code is in view controllers, not appdelegate, is this my problem?
Note: this works in the background on simulator and on a real device when the device is linked to a laptop with Xcode, and the app is launched from Xcode. It does not work on a real device by itself.
class ViewController: UIViewController, CLLocationManagerDelegate{
let manager = CLLocationManager()
#IBOutlet weak var map: MKMapView!
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.01,0.01) //shows the size of map screen
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude,location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
map.setRegion(region, animated: true)
self.map.showsUserLocation = true
print("Location Updated")
}
override func viewDidLoad() {
super.viewDidLoad()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
manager.delegate = self
manager.allowsBackgroundLocationUpdates = true
manager.pausesLocationUpdatesAutomatically = false
}
}
Can someone please help? This is one of the last issues I need to solve for one of my projects. Any links or working Github projects would be greatly appreciated! I just cant figure this one out!
SITUATION:
I followed the following tutorial:
https://www.raywenderlich.com/95014/geofencing-ios-swift
PROBLEM:
The following functions never get triggered:
AppDelegate.swift
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
if region is CLCircularRegion {
handleRegionEvent(region)
}
}
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
if region is CLCircularRegion {
handleRegionEvent(region)
}
}
func handleRegionEvent(region: CLRegion!) {
print("Geofence triggered!")
// Show an alert if application is active
if UIApplication.sharedApplication().applicationState == .Active {
if let message = notefromRegionIdentifier(region.identifier) {
if let viewController = window?.rootViewController {
showSimpleAlertWithTitle("Congratulations", message: "You just found: " + message , viewController: viewController)
}
}
} else {
// Otherwise present a local notification
let notification = UILocalNotification()
notification.alertBody = "You just found: " + notefromRegionIdentifier(region.identifier)!
notification.soundName = "Default";
UIApplication.sharedApplication().presentLocalNotificationNow(notification)
}
}
QUESTION:
The tutorial was written for iOS 8. I am currently on iOS 9.3. What caused this issue in your opinion and how do I fix it ?
You didn't show the code that you use to set up CL - which is probably where your problem lies.
Did you edit info.plist?
Are you requesting permission?
Did you call one of the start functions on the CL manager?
Make sure of two things :-
1.) You have added These to your viewDidLoad() :-
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestWhenInUseAuthorization()
locationManager.startMonitoringSignificantLocationChanges()
locationManager.startUpdatingLocation()
Another alternative to requestWhenInUseAuthorization() and startUpdatingLocation() initialisation in specific to Swift 2.2, since in Swift 2.2 the string literals for selectors is deprecated, and instead there this new operator #selector that you need to be using. :-
you can also use :-
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startMonitoringSignificantLocationChanges()
if locationManager.respondsToSelector(#selector(locationManager.requestWhenInUseAuthorization)) {
locationManager.requestWhenInUseAuthorization()
}
else {
locationManager.startUpdatingLocation()
}
//Prefer the FIRST ONE.
2.) You have updated your info.plist with :-
NSLocationAlwaysUsageDescription : String :-> I need location.
NSLocationWhenInUseUsageDescription: String :-> I need location.
privacy - location usage description: String :-> I need location.
Edit I need location according to the app's need
PS :- If it still not calls your locationManager functions
Simulator :- look for location settings of your app in your simulator settings.
Device: - Go in settings > Privacy > Location services > Your app > Always.
you also might find this explanation useful : - https://stackoverflow.com/a/26090094/6297658
initialize your location manager in app delegate on did finish launching
Same code, I'm assuming that the device is actually updating the location twice for some reason, even though I only call startUpdatingLocation() once and I run some stopUpdatingLocations() inside of didUpdateLocations
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
let loc: CLLocation = locations[locations.count - 1]
let id = 0
let type = 0
let number = 0
createNewDataPoint(id, loc: loc, type: type, number: number)
}
In this case, createNewDataPoint gets called twice, creating 2 new datapoints. It only happens once in the simulator, so I'm assuming it has something to do with the actual device and the GPS since the simulator fakes its location.
startUpdatingLocation() is only in my code one time, on a button. Basically, you click the button, go go manager.startUpdatingLocations(), didUpdateLocations hits once on simulator, twice on device (identical coordinates) and it creates 2 new data points.
The only other code that mentions anything related is setting the accuracy, filter, authorization requests, and the previously mentioned startUpdatingLocation(). Is there something I can do to make sure I'm not creating twice as many data points as necessary?
Location Manager delegate methods can be called very frequently and at any time.
You may however, apply following algorithm to safeguard yourself:
Create a global bool say didFindLocation.
Set didFindLocation to false when you call startUpdatingLocation.
Inside delegate call back didUpdateLocations:, if didFindLocation was false, set didFindLocation to true and then call stopUpdatingLocation.
Hope this helps.
The best way is do as following:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
manager.delegate = nil
}
Best solution for iOS 10.0+
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
[locationManager stopUpdatingLocation]; // stop location manager
locationManager.delegate = nil;
//Your logics...
//This will be called only one time now.
}
But don't forget to set the delegate again.
After getting the desired latitude and longitude just call stopUpdatingLocation()and set the delegate to nil.
In Swift 3:
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
In Objective-C:
[locationManager stopUpdatingLocation]
locationManager.delegate = nil
Here locationManager is the object of CLLocationManager.
You will not get frequently on simulator. and on device when you will move far away then only you get didUpdateLocations. just move in a open space so GPS can identify you device location so it get best accuracy.
Instead of starting / ending the location update and setting delegate to nil, there is a method called requestLocation which is ideal when your application need quick fix on the user's location:
From the docs:
override func viewDidLoad() {
// Create a location manager object
self.locationManager = CLLocationManager()
// Set the delegate
self.locationManager.delegate = self
}
func getQuickLocationUpdate() {
// Request location authorization
self.locationManager.requestWhenInUseAuthorization()
// Request a location update
self.locationManager.requestLocation()
// Note: requestLocation may timeout and produce an error if authorization has not yet been granted by the user
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Process the received location update
}
Use this method when you want the user’s current location but do not need to leave location services running.
#Zumry Mohamed 's solution is right
i try the code like this:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
[self.locationManager stopUpdatingLocation];
self.locationManager.delegate = nil;
self.locationManager = nil;
}
finally this delegate is called only once, i understand now why the problem is occurred, just because manager call the stopUpdatingLocationmethod but system doesn't help us to make the delegate invalid, so we can receive the callback every time location updates due to your desiredAccuracy and distanceFilter property settings of your CLLocationManager, so the final solution is just like what #Zumry Mohamed said, we can manually set the delegate to nil when we stopUpdateLocation. hope it will help you understand what happens why this could solve the problem.
locationManager.startUpdatingLocation() fetch location continuously and didUpdateLocations method calls several times,
Just set the value for locationManager.distanceFilter value before calling locationManager.startUpdatingLocation().
As I set 200 meters(you can change as your requirement) working fine
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 200
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
Another way is to set a time interval to turn on and off the delegate and so the location manager. A sorta of this
var locationManagerUpdate:Bool = false //Global
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 10 seconds
timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.updateLocationManager), userInfo: nil, repeats: true)
}
#objc func updateLocationManager() {
if locationManagerUpdate == false {
locationManager.delegate = self
locationManagerUpdate = true
}
}
extension lm_gest: CLLocationManagerDelegate {
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if locationManagerUpdate == true {
manager.stopUpdatingLocation()
manager.delegate = nil
}
//your code here...
}