Why Can I Not Get Same Locality as Apple Weather App? - cllocationmanager

I want to display the user's city & state in my application. However, I can't seem to get the desired city to populate.
I am currently in New Albany, OH. Apple's Weather app displays that correctly:
However, when utilizing CoreLocation, I am getting Columbus, OH.
Here is a map of my current location (New Albany), displaying the proximity to Columbus, OH:
When I print the received CLPlacemark, I get the following:
7070 Bayton Pl,
7070 Bayton Pl, New Albany, OH 43054, United States
# <+40.10695990,-82.82727340> +/- 100.00m,
region CLCircularRegion (identifier:'<+40.10795920,-82.82855810> radius 296.54', center:<+40.10795920,-82.82855810>, radius:296.54m)
Note - The above CLPlacemark is a little inaccurate, as the address is not my address.
However, when I print my CLPlacemark's locality, I get the following:
Columbus
I have my CLLocationManager configured as follows:
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
locationManager.distanceFilter = 3000
When I have determined that the user has authorized location services, I call:
locationManager.startUpdatingLocation()
No matter how I configure my CLLocationManager, I never get returned New Albany as the locality. I have even tried setting the CLLocationManager's desiredAccuracy to kCLLocationAccuracyBestForNavigation to see if that would help, but it did not.
What am I doing incorrectly that is causing me to receive Columbus rather than New Albany?
Update 1
I tried the solution below, however it was unreliable because the String that contains the city, state, and zip code is sometimes at the first, second, or third index, depending on the address.
Do I need to get the addressDictionary, get the array of values using FormattedAddressLines, and then parse the city manually?
Update 2
I printed the CLPlacemark that was returned, and this is what I received:
Optional([AnyHashable("Street"): 7070 Bayton Pl, AnyHashable("ZIP"): 43054, AnyHashable("Country"): United States, AnyHashable("SubThoroughfare"): 7070, AnyHashable("State"): OH, AnyHashable("Name"): 7070 Bayton Pl, AnyHashable("SubAdministrativeArea"): Franklin, AnyHashable("Thoroughfare"): Bayton Pl, AnyHashable("FormattedAddressLines"): <__NSArrayM 0x618000053fe0>(
7070 Bayton Pl,
New Albany, OH 43054,
United States
)
, AnyHashable("City"): Columbus, AnyHashable("CountryCode"): US, AnyHashable("PostCodeExtension"): 8120])
It's obvious that the city value, New Albany, in the FormattedAddressLines dictionary does not match the CLPlacemark's locality value, Columbus.
Update 3
Following is the entirety of the object that retrieves the current location:
/// Retrieves the device's location.
public final class DeviceLocation: NSObject, CLLocationManagerDelegate {
// MARK: - Properties
/// Handles the delivery of the device's current location.
private let locationManager = CLLocationManager()
/// The object responsible for handling the device's location-based updates.
public weak var deviceLocationDelegate: DeviceLocationDelegate?
// MARK: - Initialization
/// Returns an initialized `DeviceLocation` object.
public override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
}
// MARK: - CLLocationManagerDelegate
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .authorizedAlways,
.authorizedWhenInUse:
startMonitoringLocationChanges()
default:
return
}
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
print(location)
reverseGeocode(location)
deviceLocationDelegate?.deviceLocation(self, didReceiveCoordinate: location.coordinate)
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
deviceLocationDelegate?.deviceLocation(self, didFailToReceiveCoordinate: error)
}
// MARK: - Helpers
/// Requests access to the user's location while in use.
public func requestLocationAccess() {
if CLLocationManager.locationServicesEnabled() {
locationManager.requestAlwaysAuthorization()
}
}
/// Starts tasks associated with monitoring the device's location.
public func startMonitoringLocationChanges() {
locationManager.startMonitoringSignificantLocationChanges()
}
/// Retrieves information for a location from a reverse-geocode query.
/// - parameter location: The `CLLocation` instance.
private func reverseGeocode(_ location: CLLocation) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if let geocodeError = error {
print(geocodeError)
self.deviceLocationDelegate?.deviceLocation(self, didFailToReverseGeocode: geocodeError)
return
}
guard let placemark = placemarks?.last else {
return
}
self.deviceLocationDelegate?.deviceLocation(self, didFinishReverseGeocodeWithLocation: (city: placemark.locality, state: placemark.administrativeArea))
}
}
}

Related

CoreLocation Beacon callback functions not triggered when in foreground

I am new to iOS development and struggling with many of the interactions between my program and the device hardware so please excuse my very minimal knowledge.
I am trying to build into my app the ability to run code when a beacon is detected. Ultimately I need this to happen in the background as well but for now I am just working on getting it to work in the foreground.
With the code that I currently have, the print message located within the callback function is never called even with the beacon about two feet from the phone. I have double checked with an android device that the UUID broadcasted by the beacon and the UUID that iOS is searching for is one and the same.
This is the class that I created to manage the location aspects.
import CoreLocation
class LocationManager: NSObject, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
override init() {
super.init()
locationManager = CLLocationManager()
locationManager.delegate = self
}
func locationPermission() {
locationManager.requestAlwaysAuthorization()
}
func startScanning() {
let beaconRegion = CLBeaconRegion(uuid: Beacon.beaconUUID!, identifier: "CarBeacon")
print(Beacon.beaconUUID!)
locationManager.startMonitoring(for: beaconRegion)
locationManager.startRangingBeacons(in: beaconRegion)
}
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
if let beacon = beacons.first {
print(beacon.uuid)
}
}
}
This is called from a SwiftUI View with me creating the object in the beginning:
let locationManager = LocationManager()
with this in the view:
.onAppear() {
locationManager.locationPermission()
locationManager.startScanning()
}
Additionally, the UUID is from a separate file
enum Beacon {
static let beaconUUID = UUID(uuidString: "1810C112-B26E-49EB-8EB0-B8DB2DDF2DFB")
}
I also tried replacing the startScanning() function with startMonitoring():
func startMonitoring() {
let beaconRegion = CLBeaconRegion(uuid: Beacon.beaconUUID!, identifier: "CarBeacon")
locationManager.startMonitoring(for: beaconRegion)
}
and adding a new callback function:
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if let beaconRegion = region as? CLBeaconRegion, beaconRegion.uuid == Beacon.beaconUUID {
print("Hello There")
}
}
Which led to the same results of having no console output.
Many of the tutorials that I found seemed to be outdated or not written in swift and I know that the startRangingBeacons() function is already depreciated so I struggled to put random bits of information together. I would appreciate any help that you could give me.
A few things to check:
Use an off the shelf beacon scanner like Locate Beacon to confirm it can detect your transmitter. Be sure to configure your UUID with the iOS scanner app.
Go to settings -> apps -> your app -> permissions and confirm location permission is granted
Add debug lines or print statements when you start ranging and make sure you see them.

iOS permissions for user location

I am just creating an iOS app that needs to get the user location.
I have included the needed keys and values into info.plist like shown in the screenshot:
but when running the app there is a message in the debugger:
2020-04-25 18:51:16.395466+0200 Jogua[23008:1151035] This app has attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain both “NSLocationAlwaysAndWhenInUseUsageDescription” and “NSLocationWhenInUseUsageDescription” keys with string values explaining to the user how the app uses this data
Do I need to change anything else in the code?
EDIT
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
}
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
print("user latitude = \(userLocation.coordinate.latitude)")
self.defaults.set(userLocation.coordinate.latitude, forKey: "mi_latitud")
print("user longitude = \(userLocation.coordinate.longitude)")
self.defaults.set(userLocation.coordinate.longitude, forKey: "mi_longitud")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
{
print("Error \(error)")
}
As your debugger says, you are missing one entry in your info.plist. It can be misleading because the name the console prints, is not the actual key name inside info.plist.
You added:
Privacy - Location Always Usage Description
Privacy - Location Usage Description
Privacy - Location Always and When In Use Usage Description
What you need according to the debugger is:
Privacy - Location When In Use Usage Description
Privacy - Location Always and When In Use Usage Description
What you need to add in your case:
Privacy - Location When In Use Usage Description

CLRegionState is coming Unknown while requesting a state from the location manager?

I am building a feature related to region monitoring while starting region monitoring I am requesting the state as shown below in code. On some of the devices, I am getting region state Unknown all the time. If I switch Wifi On or Off or plug the charger into it. It starts working fine.
How can I make it more reliable on a cellular network?
Please, note I took all location permissions from the user before making any region monitoring or state request calls.
private func initiateLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.distanceFilter = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
}
func startMonitoring(alarm: StationAlarm) {
if LocationManager.sharedInstance.isRegionMonitoringAvailable() {
let coordinate = CLLocationCoordinate2D(latitude: stationLatitude, longitude: stationLongitude)
// 1
let region = CLCircularRegion(center: coordinate, radius: CLLocationDistance(radius * 1000), identifier: alarm.alarmId)
// 2
region.notifyOnEntry = true
region.notifyOnExit = false
// 4
locationManager.startMonitoring(for: region)
Utility.delay(0.1) { [weak self] in
self?.locationManager.requestState(for: region)
}
}
}
func locationManager(_: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
Log.event("Region State is \(state.rawValue)")
}
The issue is, you are calling the requestState using a hard-coded delay - (0.1). How do you make sure the Location Manager started monitoring your region within 0.1 seconds? You will get the exact region state of a region, only if started monitoring it.
The better method for overcoming this problem is, implement the didStartMonitoringForRegion delegate and call requestStateForRegion
locationManager.startMonitoring(for: region)
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
manager.requestState(for: region)
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if (region is CLBeaconRegion) && state == .inside {
locationManager(manager, didEnterRegion: region)
}
}
From the CLLocationManager requestState(for:) docs:
region: The region whose state you want to know. This object must be an instance of one of the standard region subclasses provided by Map Kit. You cannot use this method to determine the state of custom regions you define yourself.
You defined the region yourself so you can't use requestState(for:) to get its state. You use that function with regions that you get back from Core Location (via the delegate methods).
If you want to know whether the device is currently inside a region, start a standard location update request (startUpdatingLocation() etc) and when you get back a recent and accurate coordinate, use the CLCircularRegion contains() function to check the coordinate.
// In the locationManager(_:didUpdateLocations:) delegate method
if myCircularRegion.contains(myCoordinate) {
// ...
}

Swift 2 : iOS 8+ How to store latitude/longitude with CoreLocation and stop locating

Yeah ! My CoreLocation working now ! So i have a problem to store latitude and longitude and stopping locating.
I have a Sign In page (my MainVC)
before user sign in i need to get and store current device location and stop locating (now it's working with help of Alexander see under).
in my MainVC.swift i have this 2 global variables
var locationManager:CLLocationManager!
var myLocations = [CLLocation]()
in my viewDidLoad() i have this :
//Setup our Location Manager
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
For operate locationManager i have this 2 functions :
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("locations = \(locations)")
errorMsgIfInternetNotAvailable.text = "GPS success"
myLocations.append(locations.last!)
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
errorMsgIfInternetNotAvailable.text = "Error while updating location " + error.localizedDescription
}
When i build my app, all working, if not autorise my device to get GPS informations i have the locationManager function error working.
If i autorise it
in console, the location update every second and don't stop it with the .stopUpdatingLocation() command
First, it is always good to provide the build error output. It's hard enough to guess which error you're getting.
It seems like you defined, but not initialized myLocations property. You have to do it like this:
var myLocations = [CLLocation]()
Notice the brackets () in initialization.
And then you have to add object to your array:
myLocations.append(locations.last)
or, if you want to store only one object in your array, do it like:
myLocations = [locations.last]

Swift CLLocation does not update location at startup

I have an application were I want to get the users location in viewDidLoad, I then store the lat and long in variables and use them in a function to get data based on the position from the user. I have a timer that calls a function every x minute that gets data (let´s call it getData()), the first time getData() is called lat and long is 0, but the second, third time etc.. they have values.
When I update the coords (during runtime) in the simulator I do get the updated values for both lat and long, but it´s the first run that lat and long always are 0. My code looks like this:
let locationManager = CLLocationManager()
var getDataGroup = dispatch_group_create()
override func viewDidLoad() {
super.viewDidLoad()
dispatch_group_enter(getDataGroup)
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
dispatch_group_leave(getDataGroup)
dispatch_group_wait(getDataGroup, DISPATCH_TIME_FOREVER)
dispatch_async(dispatch_get_main_queue()) {
self.getData()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placemarks, error) -> Void in
if (error != nil){
println("Error: " + error.localizedDescription)
return
}
if (placemarks.count > 0){
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm)
}
else{
println("Error with location data")
}
})
}
func displayLocationInfo (placemark : CLPlacemark){
self.locationManager.stopUpdatingLocation()
lat = String(stringInterpolationSegment: placemark.location.coordinate.latitude)
long = String(stringInterpolationSegment: placemark.location.coordinate.longitude)
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Error: " + error.localizedDescription)
}
func getData(){
// Do stuff with lat and long...
}
For some reason the first run lat and long are both 0, the rest of the runs they have a valid value. The problem is that getData is called before the locationManager and displayLocationInfo methods even though I have added self.locationManager.startUpdatingLocation() before the call to getData() for some reason.
I don't really understand what your code is supposed to do.
There's no reason for the GCD code in your viewDidLoad. simply create an instance of the location manager and tell it to start updating your location.
Then your didUpdateLocations method will be called once a location is available. Use the last location in the array of locations you get in that method - it will be the most recent.
I would advise checking the horizontalAccuracy of the locations before using them. Typically the first few location readings are really bad, and then slowly the readings settle down. The Horizontal accuracy reading is actually a radius giving the possible area of the reading. If you get 1 km, it means your location could be anywhere in a circle 1 km in radius. You probably want to discard readings that bad and wait for one that's within a few hundred meters (or better.)
Your viewDidLoad method is calling a method getDeparturesAtStop, and that method will likely be called before the first call to didUpdateLocations fires. (You don't show what that method does.)
Where is the code that is getting a zero lat/long?

Resources