CLLocationManager make my app crash having location activated - ios

It's weird. There are some devices that crash and some other devices that not. The thing is when having location not activated the app never dies but when I allow my app access to the location in some devices crash and in other devices not.
This is the code:
override func viewDidAppear(animated: Bool) {
if CLLocationManager.locationServicesEnabled(){
switch CLLocationManager.authorizationStatus() {
case .NotDetermined, .Restricted, .Denied:
print("No access")
case .AuthorizedAlways, .AuthorizedWhenInUse:
let geocoder = CLGeocoder()
longitude = self.locationManager.location!.coordinate.longitude
latitude = self.locationManager.location!.coordinate.latitude
geocoder.reverseGeocodeLocation(CLLocation(latitude: (latitude), longitude: (longitude)), completionHandler: {placemarks, error in
if error == nil && placemarks!.count > 0 {
self.thoroughfare = (placemarks!.last?.thoroughfare)!
self.city = (placemarks!.last?.locality)!
print(self.thoroughfare)
print(self.city)
print(self.longitude)
print(self.latitude)
}
})
}
} else {
print("Location services are not enabled")
}
}
When app crashes the error points to this line:
longitude = self.locationManager.location!.coordinate.longitude
latitude = self.locationManager.location!.coordinate.latitude
I've tested the app in 10 devices, having 1-2 of them that crashes at this point.
What's happening? I think I'm managing rightly what to do and what no to do when location is or not is allowed.

You should chek if
self.locationManager.location
Is null before using it

Please try this whole code to get the location and its details.Its tried and working solution in Swift 3.0
import CoreLocation
import Foundation
class ViewController: UIViewController,CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
findMyLocation()
}
func findMyLocation(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in
if (error != nil) {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks!.count > 0 {
let pm = placemarks![0]
self.displayLocationInfo(pm)
} else {
print("Problem with the data received from geocoder")
}
})
}
func displayLocationInfo(_ placemark: CLPlacemark?) {
if let containsPlacemark = placemark {
//stop updating location to save battery life
locationManager.stopUpdatingLocation()
let locality = (containsPlacemark.locality != nil) ? containsPlacemark.locality : ""
let postalCode = (containsPlacemark.postalCode != nil) ? containsPlacemark.postalCode : ""
let administrativeArea = (containsPlacemark.administrativeArea != nil) ? containsPlacemark.administrativeArea : ""
let country = (containsPlacemark.country != nil) ? containsPlacemark.country : ""
print(" Postal Code \(postalCode)")
print(" administrativeArea \(administrativeArea)")
print(" country \(country)")
print(" locality \(locality)")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error while updating location " + error.localizedDescription)
}
Thank you

Don't declare optional values to variable. always handle errors
Don't unwrap the location
self.locationManager.location // Your error
if var longitude = self.locationManager.location.coordinate.longitude {
// do your thing
}else {
// handle the error by declaring default value
}
second thing you also might receive null values even if user lost internet while getting the location or you forget the simulate the location while testing in simulator so always handle the error

Please Check your didUpdateLocations method, you just need to check whether location is getting correctly or getting nil.
if ((self.locationManager.location) != nil){
//Get Location access here.
}

Related

CLLocationManager suddenly only returning New York, NY on Device?

This code has always worked reliably but lately (at least on my Watch) it's always returning New York, New York no matter where I am? Did something change in Core Location? 🤔
import CoreLocation
class WorkoutLocationManager: NSObject, CLLocationManagerDelegate {
private var locationManager: CLLocationManager?
public var formattedWorkoutAddress: String?
public func getWorkoutLocation() {
guard CLLocationManager.locationServicesEnabled() else {
print("User does not have location services enabled")
return
}
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
let locationAuthorizationStatus = CLLocationManager.authorizationStatus()
switch locationAuthorizationStatus {
case .authorizedAlways:
print("location authorized Always")
locationManager?.requestLocation()
case .authorizedWhenInUse:
print("location authorized When in Use")
locationManager?.requestLocation()
case .denied:
print("location authorization denied")
case .notDetermined:
print("location authorization not determined")
case .restricted:
print("location authorization restricted")
default: ()
}
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let currentLocation = locations.first else { return }
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(currentLocation) { (placemarksArray, error) in
if let unwrappedError = error {
print("Geocoder error: \(unwrappedError)")
}
guard let placemarksArrayUnwrapped = placemarksArray else { return }
if placemarksArrayUnwrapped.count > 0 {
if let placemark = placemarksArray?.first {
let locality = placemark.locality ?? ""
let state = placemark.administrativeArea ?? ""
let workoutLocationAsString = (locality + " " + state)
print("workoutLocationAsString = \(workoutLocationAsString)")
self.formattedWorkoutAddress = workoutLocationAsString
} else { print("no placemark")}
} else { print("placemark.count = 0")}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("location manager error = \(error)")
}
//I added this code below to prevent getting the error "Failure to deallocate CLLocationManager on the same runloop as its creation may result in a crash" code is from this answer: https://stackoverflow.com/questions/52304969/failure-to-deallocate-cllocationmanager-on-the-same-runloop-as-its-creation-may?noredirect=1#comment95470009_52304969
override init() {
super.init()
self.performSelector(onMainThread: #selector(initLocationManager), with: nil, waitUntilDone: true)
}
#objc private func initLocationManager() {
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
}
#objc private func deinitLocationManager() {
locationManager = nil
}
deinit {
self.performSelector(onMainThread: #selector(deinitLocationManager), with: nil, waitUntilDone: true)
}
}
I finally figured it out, I did have a default location of NY/NY set in my Scheme...I must have done it a long time ago and forgot 🤦‍♂️

How to store data (into a global variable) retrieved from reverseGeocodeLocation() method (asynchronous task)?

First I have a global variable:
var userCity : String?
Using reverseGeocodeLocation() to obtain the city name from the user's location, I am trying to store that city name information into the userCity global variable. I know that you cannot simply return data from asynchronous tasks. Instead, you would have to pass back via a block. This is what I have so far after looking at other StackOverflow posts:
//get user's location
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[locations.count - 1]
if location.horizontalAccuracy > 0 {
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
reverseCoordinatesToCity(location: location) { (city) in
self.userCity = city
}
}
}
func reverseCoordinatesToCity(location: CLLocation, completion: #escaping (_ city: String?) -> Void) {
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if (error != nil) {
print("Reverse geocoder failed with an error: " + (error?.localizedDescription)!)
completion("")
} else if (placemarks?.count)! > 0 {
let pm = placemarks![0] as CLPlacemark
completion(pm.locality!)
} else {
print("Problems with the data received from geocoder.")
completion("")
}
})
}
When I try to print userCity in a different method. For example:
func someFunction() {
print(userCity!)
}
Nothing is printed out and it seems that userCity is nil. I've spent almost three hours trying to figure this out. My main goal is to pass this data onto another ViewController using a segue.
Any help is appreciated. Thank you.
As #rmaddy has said you are not using city when its ready. The way to fix that is to hinge your view controller logic from the CLGeocoder callback. It's also worth setting your location manager to nil if you don't want to use it anymore because stopUpdatingLocations() doesn't always stop location updates dead in its tracks.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[locations.count - 1]
if location.horizontalAccuracy > 0 {
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
locationManager = nil //this makes sure you don't get more updates
reverseCoordinatesToCity(location: location)
}
}
func reverseCoordinatesToCity(location: CLLocation) {
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if (error != nil) {
print("Reverse geocoder failed with an error: " + (error?.localizedDescription)!)
} else if (placemarks?.count)! > 0 {
let pm = placemarks![0] as CLPlacemark
city = pm.locality!
//use city here with your view controllers
} else {
print("Problems with the data received from geocoder.")
}
})
}

How to get current location among all WIFI, GPS, Location Services or any other apps in iOS?

I need to get the user's get current location dynamically.
For this I am working with CLLocationManager for getting current location it is fine.
Now I want to get the current location using WIFI, GPS, Location Services, or from any other apps in the device(i.e that apps are already maintained the current location value).
In above all possible ways which one is maintained the less battery usage for getting current location?
How is it possible in iOS?
Put this code in Appdelegate
func initLocationManager() {
seenError = false
locationFixAchieved = false
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if (locationFixAchieved == false) {
locationFixAchieved = true
let locationArray = locations as NSArray
let locationObj = locationArray.lastObject as! CLLocation
let coord = locationObj.coordinate
NSUserDefaults.standardUserDefaults().setValue(String(coord.latitude), forKey: "latitude")
NSUserDefaults.standardUserDefaults().setValue(String(coord.longitude), forKey: "longitude")
locationManager.stopUpdatingLocation()
}
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in
if (error != nil) {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks!.count > 0 {
let pm = placemarks![0] as CLPlacemark
self.displayLocationInfo(pm)
} else {
print("Problem with the data received from geocoder")
}
})
}
func displayLocationInfo(placemark: CLPlacemark) {
//stop updating location to save battery life
locationManager.stopUpdatingLocation()
print(placemark.locality )
print(placemark.postalCode )
print(placemark.administrativeArea )
print(placemark.country )
NSUserDefaults.standardUserDefaults().setValue(placemark.locality, forKey: "city")
NSUserDefaults.standardUserDefaults().setValue(placemark.administrativeArea, forKey: "state")
}
// authorization status
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
}
NSNotificationCenter.defaultCenter().postNotificationName("LabelHasbeenUpdated", object: nil)
if (shouldIAllow == true) {
print("Location to Allowed")
// Start location services
locationManager.startUpdatingLocation()
} else {
print("Denied access: \(locationStatus)")
}
}
Now call initLocationManager() class in didFinishLaunchingWithOptions

Location without internet connection

I want to get the location of the user when press a button, I found a way to do that with CLLocationManager, that code work perfectly until the device the device doesn't have network connection, the function func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) catch an error
((NSError?) error = domain: "kCLErrorDomain" - code: 2 { _userInfo =
nil })
How can I get the device location without internet connection? here is the code I use
import CoreLocation
class SesionViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: { (placemarks, error) -> Void in
if (error != nil) {
print("Error:" + error!.localizedDescription)
return
}
if placemarks!.count > 0 {
let pm = placemarks![0] as CLPlacemark
print(pm!.coordinate.latitude)
}else {
print("Error with data")
}
})
}
func startButtonPress(sender:UIButton!){
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.locationManager.stopUpdatingLocation()
}
}
Edit
I already found the solution, I just don't execute CLGeocoder().reverseGeocodeLocation... just catch the location with the locations var like this
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(manager.location!.coordinate.latitude)
}
from Apple doc about ClGeocoder
The computer or device must have access to the network in order for
the geocoder object to return detailed placemark information
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: { (placemarks, error) -> Void in
if (error != nil) {
print("Error:" + error!.localizedDescription)
return
}
if placemarks!.count > 0 {
let pm = placemarks![0] as CLPlacemark
print(pm!.coordinate.latitude)
}else {
print("Error with data")
}
})
will always report an error without an internet access. Your location data are still available (if your device has GPS unit)

CLLocationManager region monitoring delegate class issue

I have implemented a custom class around CoreLocation to do iBeacon region monitoring.
This class has some custom properties that i'm using to store some information related to beacons for later use (during entry and exit events).
The problem that I'm facing is when the app is terminated or kept in background these stored properties are no more available. By that I mean, let's say the app found a beacon region while in background/terminated, as usual the app will be launched in the background for us to process. I wanted to use the stored properties for custom operations during that time.
Did anyone faced this issue before? Am I doing this in a wrong way? Also, I am using this class from a cocoapod library that I'm currently working.
Below is the class that I wrote.
#available(iOS 9.0, *)
class BeaconManager: NSObject, CLLocationManagerDelegate {
//these properties are becoming nil
private var manager: CLLocationManager
private var lastDetection: NSDate?
private var isMonitoring = false
private var repository: [String: DBeacon]
private var monitoredRegions: [String: DBeacon] becoming nil
private var notifyBackground = true
static let sharedManager = BeaconManager()
weak var delegate:BeaconProtocol?
private override init() {
manager = CLLocationManager()
repository = [:]
monitoredRegions = [:]
if CLLocationManager.authorizationStatus() != .AuthorizedAlways {
manager.requestAlwaysAuthorization()
}
super.init()
manager.delegate = self
}
func startMonitoringForBeacon(beacon: Beacon) throws {
guard CLLocationManager.locationServicesEnabled() else {
CFLogger.ERROR("Location services not enabled")
throw BeaconErrorDomain.AuthorizationError(msg: "Location services not enabled")
}
guard CLLocationManager.authorizationStatus() == .AuthorizedAlways else {
switch CLLocationManager.authorizationStatus() {
case .Denied:
throw BeaconErrorDomain.AuthorizationError(msg: "User denied location services")
case .Restricted:
throw BeaconErrorDomain.AuthorizationError(msg: "App is prevented from accessing Location Services")
default:
throw BeaconErrorDomain.AuthorizationError(msg: "App doesn't have authorization to monitor regions")
}
}
guard CLLocationManager.isMonitoringAvailableForClass(CLBeaconRegion) else {
CFLogger.ERROR("Region monitoring not available on this device")
throw DBeaconKitErrorDomain.RegionMonitoringError(msg: "Region monitoring not available on this device")
}
guard let auuid = NSUUID(UUIDString: beacon.uuid) else {
throw BeaconErrorDomain.InvalidUUIDString
}
let region:CLBeaconRegion!
switch (beacon.major, beacon.minor) {
case (.None, .None):
region = CLBeaconRegion(proximityUUID: auuid, identifier: dbeacon.identifier)
case (.Some(let major), .None):
region = CLBeaconRegion(proximityUUID: auuid, major: UInt16(major), identifier: beacon.identifier)
case (.Some(let major), .Some(let minor)):
region = CLBeaconRegion(proximityUUID: auuid, major: UInt16(major), minor: UInt16(minor), identifier: beacon.identifier)
default:
throw BeaconErrorDomain.InvalidDBeaconInfo
}
region.notifyEntryStateOnDisplay = false
region.notifyOnEntry = true
region.notifyOnExit = true
repository[beacon.identifier] = beacon
manager.startMonitoringForRegion(region)
}
func stopMonitoringForBeacons(beacons: [Beacon]) {
guard isMonitoring else {
return
}
beacons.forEach { (dbeacon) -> () in
stopMonitoringForBeacon(beacon)
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
guard let handler = delegate else {
return
}
handler.initializationFailed(error)
}
func locationManager(manager: CLLocationManager, didStartMonitoringForRegion region: CLRegion) {
guard let aregion = region as? CLBeaconRegion, beacon = repository[aregion.identifier] else {
return
}
isMonitoring = true
monitoredRegions[aregion.identifier] = beacon
manager.requestStateForRegion(region)
}
func locationManager(manager: CLLocationManager, monitoringDidFailForRegion region: CLRegion?, withError error: NSError) {
guard let aregion = region as? CLBeaconRegion, beacon = repository[aregion.identifier], handler = delegate else {
return
}
handler.monitoringFailedForRegion(beacon, error: error)
}
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
guard let aregion = region as? CLBeaconRegion else {
return
}
guard let beacon = monitoredRegions[aregion.identifier] else {
return
}
guard let handler = delegate else {
print("Handler not available to report beacon entry event \(region.identifier)")
return
}
print("Entered beacon region \(beacon)")
handler.entered(beacon)
}
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
guard let aregion = region as? CLBeaconRegion, beacon = monitoredRegions[aregion.identifier], handler = delegate else {
print("Handler not available to report beacon exit event \(region.identifier)")
return
}
print("Exited beacon region \(beacon)")
handler.exited(beacon)
}
}
I ended up finding that stored properties doesn't have the values that I set while initiating region monitoring.
Any help is truly appreciated.
Regards.
The properties are losing their initialization values on app restarts as #Paulw11 said. The typical way to handle this is to store these properties into NSUserDefaults. The snippet below shows how you would restore the lastDetection field in the bottom of the init method. A second method called save() would have to be called to persist that field once changed.
private override init() {
manager = CLLocationManager()
repository = [:]
monitoredRegions = [:]
if CLLocationManager.authorizationStatus() != .AuthorizedAlways {
manager.requestAlwaysAuthorization()
}
super.init()
manager.delegate = self
let userDefaults = NSUserDefaults.standardUserDefaults()
lastDetection = userDefaults.valueForKey("last_detection") as! NSDate?
}
func save() {
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setValue(lastDetection, forKey: "last_detection")
}
The example above only shows saving and restoring a single of your properties. You would need to do this with all of them, and some would be more complicated to deal with (like monitoredRegions and repository) because they are complex data types that can't directly be serialized to NSUserDefaults. To do this serialization, you might try using JSON to convert them to a string you can store and then parse them out from that same string.

Resources