locationManager:didUpdateLocations: always be called several times - iphone

I start updating current location when the view did appear, and stop updating location whenever locationManager:didUpdateLocations: is called. But why the locationManager:didUpdateLocations: always be called several times? What have I missed?
#import "ViewController.h"
#interface ViewController (){
CLLocationManager *locationManager; // location manager for current location
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startUpdatingCurrentLocation];
}
- (void)startUpdatingCurrentLocation
{
if (!locationManager)
{
locationManager = [[CLLocationManager alloc] init];
[locationManager setDelegate:self];
locationManager.distanceFilter = 10.0f; // we don't need to be any more accurate than 10m
}
[locationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
[locationManager stopUpdatingLocation];
}
#end

Probably it depends about the accuracy you set to the locationManager. You have 3 kinds o localization Cell Radio, WiFi Map, GPS. If you set best as accuracy the location manager will continue to check you position, if the location with better accuracy is out of the range of the distance filter the delegate method will be called again.

SWIFT version
i made a helper class as HelperLocationManager and added a notification- observer pattern
import UIKit
import CoreLocation
class HelperLocationManager: NSObject {
var locationManager = CLLocationManager()
static let sharedInstance = HelperLocationManager()
var currentLocation :CLLocation?
var notGotUserLocation = true
override init() {
super.init()
var code = CLLocationManager.authorizationStatus()
if code == CLAuthorizationStatus.NotDetermined {
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
}
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
locationManager.distanceFilter = 100;
}
}
extension HelperLocationManager: CLLocationManagerDelegate{
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locValue = locations.last as! CLLocation
println(locValue)
self.currentLocation = locValue
NSNotificationCenter.defaultCenter().postNotificationName("sendCurrentAddressToViewController", object:self.currentLocation)
notGotUserLocation = false
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Your error is ", error.localizedDescription)
}
}
Now if your Viewcontroller class needs the location then put an observer there
var helperLocation:HelperLocationManager?
in viewDidLoad as
override func viewDidLoad() {
helperLocation = HelperLocationManager()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "getCurrentAddressToViewController:", name: "sendCurrentAddressToViewController", object: nil)
}
//and observer as
func getCurrentAddressToViewController(notification: NSNotification) {
currentLocation = notification.object as? CLLocation
NSNotificationCenter.defaultCenter().removeObserver(self, name: "sendCurrentAddressToViewController", object: nil)
}
//although didUpdateLocation is called multiple times you only get one time location because of removing observer after you get the location.
EDIT: I refractored this helper class so that you dont need to add notificationobserver pattern
class HelperLocationManager: NSObject {
private lazy var locationManager = CLLocationManager()
static let sharedInstance = HelperLocationManager()
var currentLocation :CLLocation?
override init() {
super.init()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
}
}
extension HelperLocationManager: CLLocationManagerDelegate{
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.NotDetermined:
locationManager.requestWhenInUseAuthorization()
case CLAuthorizationStatus.Restricted:
PrinterHelper.messagePrinter("Restricted Access to location")
case CLAuthorizationStatus.Denied:
PrinterHelper.messagePrinter("User denied access to location")
case CLAuthorizationStatus.AuthorizedWhenInUse:
if #available(iOS 9.0, *) {
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
}
default:
PrinterHelper.messagePrinter("default authorization")
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue = locations.last
HelperLocationManager.sharedInstance.currentLocation = locValue
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
PrinterHelper.errorPrinter(error.localizedDescription)
}
}
View Controller where you need to get user Permission
var helperLocationManager:HelperLocationManager?
in viewDidLoad as
override func viewDidLoad() {
helperLocationManager = HelperLocationManager.sharedInstance
}
And to get the location you need to call the singleton property currentLocation as
if let userCurentLoc = HelperLocationManager.sharedInstance.currentLocation{
//userCurrentLoc is the user Location
}

To complement on Anish's answer, if you wanna know when your helper class didn't call the location update (i.e when you have turned off your location services), you can manage this using the locationManager:didChangeAuthorizationStatus: method, and if your location is not allowed, you can call another notification- observer
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
var shouldIAllow = false
switch status {
case CLAuthorizationStatus.Restricted:
locationStatus = "Restricted Access to location"
case CLAuthorizationStatus.Denied:
locationStatus = "User denied access to location"
case CLAuthorizationStatus.NotDetermined:
locationStatus = "Status not determined"
default:
locationStatus = "Allowed to location Access"
shouldIAllow = true
}
if (shouldIAllow == true) {
print("Location to Allowed")
// Start location services
locationManager!.startUpdatingLocation()
} else {
print("Denied access: \(locationStatus)")
NSNotificationCenter.defaultCenter().postNotificationName("deniedLocation", object:locationStatus)
}
}

Related

macos CLLocationManager Promt

I want to send location details when a push notification receives from server. But macos application doesn't ask location permission in launching. Added all privacy items in info.plist. It ask for permission when it calls locationmanager.startUpdatingLocation().And didn't ask again if i cancel it.The code is given below.
class AppDelegate: NSObject, NSApplicationDelegate, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
func applicationDidFinishLaunching(_ aNotification: Notification) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func scanLocationRequest{
locationManager.startUpdatingLocation()
// Call this when a notification receives.
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let currentLocation = locations.last
locationManager.stopUpdatingLocation()
sendLocationReport(currentLocation: currentLocation!)
}
}
To check if user have location access or not use following code:
var isPermissionAvailable: Bool {
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedAlways, .authorizedWhenInUse:
return true
case .denied, .restricted, .notDetermined:
requestForLocation()
return false
}
}
func requestForLocation() {
// Edit
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
To show pop-up at App launch, you can use following code in applicationDidFinishLaunching(_ aNotification:) in AppDelegate class:
if isPermissionAvailable {
// Do your work on permission available
}

How to access user coordinates

I am trying to access the users coordinates using:
import MapKit
import CoreLocation
override func viewDidLoad() {
super.viewDidLoad()
let locationManager = CLLocationManager()
let userCoordinates = (locationManager.location?.coordinate)!
}
However, it crashes upon the simulator loading. I set the simulator location to Apple, and input privacy keys into info.plist, but I am not sure why this is not grabbing the user location.
There are a few things you need to do first before you can start using a device's current geolocation safely and based on the code you provided I'm going to assume that you might be missing some so here is a common set up using a Google Map, which acts like any other basically:
class YourViewController: UIViewController, CLLocationManagerDelegate {
// properties
var locationManager = CLLocationManager()
var currentCoordinate: CLLocationCoordinate2D?
// load view
override func loadView() {
addLocationManager()
}
// add location manager
func addLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
// location manager delegate: did change authorization
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("LOCATION ACCESS RESTRICTED")
case .denied:
print("LOCATION ACCESS DENIED")
case .notDetermined:
print("LOCATION ACCESS NOT DETERMINED")
case .authorizedAlways:
fallthrough
case .authorizedWhenInUse:
print("LOCATION STATUS GRANTED")
}
}
// location manager delegate: did fail with error
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("LOCATION ACCESS ERROR: \(error)")
}
// location manager delegate: did update locations
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last!
// unhide map when current location is available
if mapView.isHidden {
mapView.camera = GMSCameraPosition.camera(withLatitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude, zoom: 18, bearing: 0, viewingAngle: 0)
mapView.isHidden = false
}
// update current location properties
currentCoordinate = lastLocation.coordinate
}
}
And no need to import CoreLocation if you've imported MapKit.

didUpdateHeading not called in swift

I am trying to get the heading information of the device in order to use the difference between the magnetic and true heading to determine the declination for the user location. For this, I have a Helper class and my MainVc (mvc). In my helper class init method I do the following:
...
...
locationManager = CLLocationManager()
switch CLLocationManager.authorizationStatus() {
case .AuthorizedAlways:
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
case .NotDetermined:
locationManager.requestAlwaysAuthorization()
case .AuthorizedWhenInUse, .Restricted, .Denied:
mvc.alertDenied();
}
if (!initialized)
{
initialized = true;
self.performSelector("finishUpdatingLocation", withObject: nil, afterDelay: 2.0);
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print ("didUpdateLocations")
var userLocation:CLLocation = locations[0] as! CLLocation
longitude = Float(userLocation.coordinate.longitude);
latitude = Float(userLocation.coordinate.latitude);
}
func finishUpdatingLocation () {
locationManager.stopUpdatingLocation();
NSObject.cancelPreviousPerformRequestsWithTarget(self, selector: "finishUpdatingLocation", object: nil);
mvc.goingToUpdateHeading();
locationManager.startUpdatingHeading();
}
didUpdateLocations is being called and I am successfully fetching the device's coordinates. However although I have added didUpdateHeading, the first line in it is not being printed, the device's stuck at that point:
func locationManager(manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
print("didUpdateHeading");
if (newHeading.headingAccuracy > 0) {
variation = newHeading.trueHeading - newHeading.magneticHeading;
doneLoading = true;
locationManager.stopUpdatingHeading();
mvc.goingToCalculateData();
if (calcData) {
calculateData();
print ("Calculating data");
calcData = false;
}
}
print(newHeading.magneticHeading)
}
Any ideas why the startUpdatingHeading is not being called?
You have to call locationManager.startUpdatingHeading() like
...
...
locationManager = CLLocationManager()
switch CLLocationManager.authorizationStatus() {
case .AuthorizedAlways:
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()
case .NotDetermined:
locationManager.requestAlwaysAuthorization()
case .AuthorizedWhenInUse, .Restricted, .Denied:
mvc.alertDenied();
}
If you are checking it on simulator then it will not work please check it on real device.

Core Location returns nil when asked for location

My private constant in my Custom class:
private let locationManager = CLLocationManager()
in viewDidLoad() I call the method:
private func setupLocationManager() {
locationManager.delegate = self
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
But when I ask about location at some time later:
println("-----\(locationManager.location)")
I get:
-----nil
Where is the problem? I run this on device. My app is allowed to use location...
You should not try to get location that way because getting location can take some time (for more information see GPS TTFF). Since your location manager already has a delegate you must get location in didUpdateLocations delegate method. Also always make sure you have the permission to use location before calling startUpdatingLocation
private func setupLocationManager() {
locationManager.delegate = self
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
if CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse || CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways{
locationManager.startUpdatingLocation()
}else{
locationManager.requestWhenInUseAuthorization()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!){
println(manager.location)
}
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == CLAuthorizationStatus.AuthorizedWhenInUse || status == CLAuthorizationStatus.AuthorizedAlways {
locationManager.startUpdatingLocation()
}
}

How to request a start for startUpdatingLocation?

I'm starting a location manager as i open the view with this code:
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var userLocation: CLLocation = locations[0] as! CLLocation
userLatitude = userLocation.coordinate.latitude
userLongitude = userLocation.coordinate.longitude
if (userLocation.horizontalAccuracy < 80) {
manager.stopUpdatingLocation()
} else {
addMapRegionAndAnnotation()
}
}
So this means the locationManager does Stop if the location is detected in a 80m radius.
Now i want to start this locationManager again if i press my updateLocation button:
#IBAction func updatePostMapView(sender: AnyObject) {
manager.startUpdatingLocation()
}
Then the app shuold update the user location till the radius is smaller then 80m again...
But as you may expect, this isn't working because i can't call the manager.startUpdatingLocation() from outside of the locationManager.
Has anyone a solution?
You can call startUpdatingLocation() or stopUpdatingLocation() from inside of any method. First You must create location manager within your class,
class
let locationManager = CLLocationManager()
func initializeLocationManager()
{
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.distanceFilter=kCLLocationAccuracyNearestTenMeters
}
func anyMethod()
{
locationManager.stopUpdatingLocation()
locationManager.startUpdatingLocation()
}