How can I get trueHeading just once in iOS? - ios

As a fresh iOS developer, I am confused to get trueHeading data recently.
class AR: CLLocationManagerDelegate{
var heading:Float!
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
heading = Float(newHeading.trueHeading)
}
}
Via above code, I can continuously get current device's direction. However, can I just get the trueHeading just once? In addition, I tested the trueHeading, it will be accurate after seconds. Can I get the trueHeading at a particular time?
Thank you for any help!

Whenever you need heading you can call
[self.locationManager startUpdatingHeading];
Once you have received the heading, you can do a
[self.locationManager stopUpdatingHeading];
To receive the latest derived heading,
self.locationManager.heading;
So ideally, you call startUpdatingHeading, show a loader or something to the user.
Once you get a heading in didUpdateHeading, relay a message to the controller via a notification or something, and stopupdating heading.
You cannot set the accuracy of the Heading, but you can set the headingFilter to only update when there is a large distance.
/* Notify heading changes when heading is > 5.
* Default value is kCLHeadingFilterNone: all movements are reported.
*/
self.locationManager.headingFilter = 5;
Ideally, you do not stop ever, you continue accessing the heading, with a filter of 5, ensuring it does not drain battery.
You store this in the defaults lets say, and use it whenever needed and update it.
If you need it just for once, then you may do a quick fix by waiting for some duration or lets say some hits to the function, but that is not an ideal solution.
Maybe this can also help, but it is primarily for distance, and not heading. Heading is supposed to be constantly updated.
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
If you want this on app launch, do it in the app delegate.

Related

CLLocation Manager giving incorrect location sometime

I am using core location API for iOS (CLLocation manager) and its sometimes giving me wrong location like if I am in U.S it gives me latitude and longitude of Spain. Searched stack over flow for the same but couldn't get a relevant answer for my query. Being novice to the use maps and locations I am a bit confused. Any help would be highly appreciated.
I assume that you are using a physical device and not the simulator to test this, and you did not select the option to simulate location in xcode. Sometimes CLLocation manager can return for example an old cached location, so perhaps that is what happened to you. Before you do anything with received CLLocation object I recommend checking its horizontalAccuracy and timestamp properties.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let lastLocation = locations.last, lastLocation.horizontalAccuracy < 1000, abs(lastLocation.timestamp.timeIntervalSinceNow) < 60 else {
// location not accurate or to old (cached previously) so do not do anything
return
}
// do something
}
Actually, Apple use wifi devices around for location detection. So on "cold" start gps chip needs some time to start and find satellites, and during that time device use location based on router's mac addresses around. If your (or your neighbor's) router ever been in Spain, than that's the reason - Apple database of mac addresses refresh very slowly, it could take 1-3 months for that.

CLLocationManager Region Monitoring: Detect Arrival in Suspended State

I'm looking for a way that I can track that a user has arrived near a designated set of co-ordinates. The functionality needs to work while the application is in the background (preferably within 100 metres). Also, to preserve the battery, I ideally do not want to get too many co-ordinate readings (perhaps a reading every 10 minutes for no longer than a couple of hours).
There are a couple of ways that I have tried to accomplish this task, but have been unable to obtain the desired result:
Background Timer:
I had added a background task in (App.delegate)
func applicationDidEnterBackground(_ application: UIApplication)
Which executed a repeated Timer.scheduledTimer to get co-ordinates and process
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
to detect if the user was within range. This method worked if applied in the short-term, but only until the application was suspended, which was about 3 minutes. Ideally, I would not want to get co-ordinates this frequently.
Region Monitoring:
I had initialised the CLLocationManager as shown below:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.activityType = .otherNavigation
locationManager.requestAlwaysAuthorization()
}
The LocationManager starts when the application enters into the background:
func applicationDidEnterBackground(_ application: UIApplication) {
self.monitorRegionAtLocation(center: CLLocationCoordinate2D(latitude: x, longitude: y), identifier: id)
locationManager.startUpdatingLocation()
}
Code for monitoring of region:
func monitorRegionAtLocation(center: CLLocationCoordinate2D, identifier: String ) {
// Make sure the app is authorized.
if CLLocationManager.authorizationStatus() == .authorizedAlways {
// Make sure region monitoring is supported.
if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
// Register the region.
let maxDistance = 200.0
let region = CLCircularRegion(center: center,
radius: maxDistance, identifier: identifier)
region.notifyOnEntry = true
region.notifyOnExit = false
locationManager.startMonitoring(for: region)
}
}
}
And I added a didEnterRegion function block for CLLocationManager:
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if let region = region as? CLCircularRegion {
let identifier = region.identifier
print("FOUND: " + identifier)
}
}
The code appears to work for detecting entry into a region, however the co-ordinates are not updating while in the background.
Additional Information
I have the Background Modes of Location Updates and Background Fetch enabled
I have supplied values for 'Location Always Usage Description' and 'Location When in Use Usage Description' in the Info.plist
The App Settings shows 'Always' permission against the Location
I believe that there has to be a better way of operating these kinds of checks in the background, but I haven't discovered any method of detecting other movements in the background.
Any direction on this matter would be greatly appreciated, and if you need any more information, please let me know and I'll provide what I can.
UPDATE:
I have modified the approach following the advice of comments below to use Region Monitoring.
Any location update/monitoring requires it's location manager to be configured properly so that it can work to the best to provide the desired location update. It's important to check some point when doing background location update:
1. Check background modes of location updates and background fetch should be enable
2. Check 'Location Always Usage Description' and 'Location When in Use Usage Description' in the Info.plist should be provided
3. Check if you want to pause in between location update - if yes then you need to provide activity type so that location manager can determine best way to pause location update for you
4. Check if you want to apply distance filter - you want user(device) to move some minimum amount for location manager to send updated location
5. Check if you want desired accuracy- This may cause power drain for certain accuracy type
In your code I can see location manager is configured with some of the parameter but missing accuracy and distance filter for background mode.
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.activityType = .otherNavigation
Also, if you see pause location update property in Apple doc it says:
For apps that have in-use authorization, a pause to location updates
ends access to location changes until the app is launched again and
able to restart those updates. If you do not wish location updates to
stop entirely, consider disabling this property and changing location
accuracy to kCLLocationAccuracyThreeKilometers when your app moves to
the background. Doing so allows you to continue receiving location
updates in a power-friendly manner.
Essentially it tells that if you want disable pause then you have to keep accuracy level (kCLLocationAccuracyThreeKilometers). Which I guess is missing in your approach.
Also, you can check this link which actually starts background task and then starts location manager monitoring inside the background task.
Hope it helps.
The question says "CLLocationManager Region Monitoring: Detect Arrival in Background". And this is very much possible, but detecting anything after being killed is not possible (from iOS 7).
Whenever user swipe ups your app app-switcher, iOS takes it as the user doesn't wish the app to be running in the background, and so all the call-backs are stopped.
This answer, this answer and this answer also says the same thing. However Apple doc is a little confusing.
My personal observation is that app gets called even in Killed mode but very rarely.
And about getting the location, whenever the delegate method of geofencing is called, you can get location easily.
And the background modes are really not needed for your requirement.
And unfortunately (fortunately for iOS user as they save battery) we don't really have a way to get location just for 1 hr after app being killed.

Which one to use "locations array" or "manager.location.coordinate" in Swift in iOS to get user location?

I want to get the user's location. It might be approximate position, that's fine.
In didUpdateLocations method, i saw two ways to get the coordinates.
Using manager.location.coordinate
Using locations array
Which one should I go for? I am thinking locations array will contain user's recent location as I am starting startUpdatingLocation() as soon as user opens the app.
So which one should i use to get the coordinates? And what does manager.location.coordinate do in this case?
Note: Accuracy of location need not be accurate to 10 meters. Rough estimation is enough.
Here is my code:
func initLocationUpdates() {
if CLLocationManager.locationServicesEnabled() {
print("going to get location")
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
else {
print("location services disabled")
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
print("locations = \(locValue.latitude) \(locValue.longitude)")
/*
let userLocation:CLLocation = locations[0] // or maybe even locations.last
let long = userLocation.coordinate.longitude;
let lat = userLocation.coordinate.latitude;
print("lat: ", lat, " --- long: ", long)
*/
locationManager.stopUpdatingLocation()
}
I'd prefer the array over the instance variable, because of the way desiredAccuracy works - it doesn't guarantee that the location updates you receive will have the accuracy you request - merely that the system will make its best effort to provide them. Depending on how the location manager behaves, you might get a number of updates in the locations array, with differing levels of accuracy. You can then filter the array to find the most accurate one.
The key point is that if multiple updates arrive in the same didUpdateLocations: call, and you just use manager.location, you might be missing an update. The missing update would definitely not be the most recent one, but it might be the most accurate one received so far.
Since you have set desiredAccuracy to a very high level (ten meters), I'm guessing precision is more important to you than timing - it will take a while for the system to deliver an update of that level of accuracy (and it may never arrive if you are indoors or otherwise blocked from using GPS). So the likelihood of the scenario above occurring is reasonable.
As for the purpose of the instance variable location, the docs suggest that it's intended to be used in certain circumstances when the app restarts:
In iOS 4.0 and later, this property may contain a more recent location object at launch time. Specifically, if significant location updates are running and your app is terminated, this property is updated with the most recent location data when your app is relaunched (and you create a new location manager object). This location data may be more recent than the last location event processed by your app.

Latidude and Longitude are incorrect first time app is launched ios

I am using CLLocationManager to get the user's current location. I need the user's current location because I am using the Open Weather Api to display the weather data wherever the user is. My problem is that the first time I open the app the lat and long value are 0, 0. But then if I run the app again it works fine. So only on the first launch of the app incorrect lat and long values are used. I do all my set up in viewDiDLoad, here is the code...
locationManager = [[CLLocationManager alloc] init];
[locationManager requestAlwaysAuthorization];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
[self->locationManager requestWhenInUseAuthorization];
[locationManager startUpdatingLocation];
I don't know what I am doing wrong. Is it something to do with getting the lat and long in the view controller instead of App Delegate?
Here is how I am getting the location values:
locationManager.location.coordinate.latitude
locationManager.location.coordinate.longitude
It's happening because when you launch your app first time Location manager asks for user permission to access current location and when user allows - It takes little time in process of fetching current location and you're accessing coordinates before completion of that process.
When you launch your app again Location manager detects that you already gave the permission so it gives you current location coordinate very quickly.
So the solution is - you've to wait until it completes location update opt. You can put some kinda delay there using dispatch OR can use Location manager delegate method didUpdateUserLocation OR if your UIViewController isn't the initial view controller - in this case you should implement this process in AppDelegate, use your code in didFinishLaunching, so when you'll open your view controller (whatever navigation flow you're using) location details will be there cause in meantime location manager will track your location.
Hope it'll help you!
You should check horizontalAccuracy of the CLLocation object for a negative value and do not use the lat/lon value if that is the case. It is highly likely that when you see the lat/lon values are 0/0, the GPS hasn't secured a location lock.

didUpdateToLocation called only twice on 3G

the problem is when i turn internet connection from wifi to 3G, location does not updating and did Update To Location is never called.anyone can help me? i have to send updated current location to server through web service after every 5 seconds.web service is calling but every time the same coordinates goes to server.i want to send coordinates of updated location but on 3G the location update method never called when i change location
[mapview animateToLocation:newLocation.coordinate];
Currentmarkers.position = CLLocationCoordinate2DMake(newLocation.coordinate.latitude,newLocation.coordinate.longitude);
deleg.currentCoordinates = [[CLLocation alloc]initWithLatitude:newLocation.coordinate.latitude longitude:newLocation.coordinate.longitude];
Following is the code which i used to create location manager :
currentLocation = [[CLLocationManager alloc]init];
currentLocation.distanceFilter = kCLDistanceFilterNone;
currentLocation.desiredAccuracy = kCLLocationAccuracyHundredMeters; // 100 m
currentLocation.delegate = self;
[currentLocation startUpdatingLocation];
Switching from WiFi to 3G should not have anything to do with it.
You need to post your code that creates the location manager, configures it, sets you up as the delegate, as well as your location manager delegate methods. If you have the desiredAccuracy or the distanceFilter values set high enough, you won't get notified until you move quite a distance.
Try setting the accuracy to the "best" value, and the distance filter value to kCLDistanceFilterNone for testing.
You may also need to add a scrolling text view to your interface that logs location updates, and then take your device and go for a walk of a kilometer or so. Then you should move var enough to be sure to get location updates.

Resources