CLLocationManager crashing on release, but why? - iphone

This might be one of those silly question where, once a solution is pointed out, makes you feel pretty stupid wondering how you didn't see it but I can't figure out why this part of my app is crashing with EXC_BAD_ACCESS (and no stack trace).
I have a CLLocationManager *locationManager (ivar declared in interface file) that gets created on viewDidLoad if locationServices is enabled:
- (void)viewDidLoad
{
[super viewDidLoad];
if ([CLLocationManager locationServicesEnabled])
[self findUserLocation];
...
}
#pragma mark - Location finder methods
- (void)findUserLocation
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
[locationManager startUpdatingLocation];
}
So the location manager starts updating location and each time and update is found, the delegate method below is called, where I check to see if I should time out or continue looking for my desiredAccuracy:
#pragma mark - CLLocationManager delegates
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if ([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp] > 8)
[self locationManagerTimeOut];
else if ((newLocation.horizontalAccuracy <= manager.desiredAccuracy) && (newLocation.verticalAccuracy <= manager.desiredAccuracy))
[self locationManagerLockedPosition];
}
If a position is locked, this method is called:
- (void)locationManagerLockedPosition
{
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
[locationManager release], locationManager = nil;
NSLog (#"add results to view");
}
If it times out, this is the method called:
- (void)locationManagerTimeOut
{
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
[locationManager release], locationManager = nil;
NSLog (#"Time out!");
}
Problem is, in either case (time out or locked position), I get the NSLog output in the console and then 2 secs later the app crashes??
Interesting thing is, if I comment out my [locationManager release]... line, everything works fine but WHY? Also if I move the [locationManager release] to my dealloc method, no crashes either!
Am I missing something basic here?
Thanks!
Rog

I had the same issue and there's probably some problem in the depths of CLLocationManager. Fixed by doing:
[locationManager stopUpdatingLocation];
[self performSelector:#selector(discardLocationManager) onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO];
and in discardLocationManager do:
- (void) discardLocationManager
{
locationManager.delegate = nil;
[locationManager release];
}

You are release the CLLocationManager instance from within a callback method, which can't be a good idea.
The CLLocationManager calls your callbacks locationManager:didUpdateToLocation:fromLocation etc. If you release the location manager instance, you're basically deallocating the object that just called you. Bad idea. That's why the app crashes.
Instead of releasing the location manager instance, you could autorelease it.
Sargon

Related

Getting Longitude and Latitude Values

I am trying to connect to GPS from my code. I am doing so according to this tutorial.
- (void)viewDidLoad {
[super viewDidLoad];
CLController = [[CoreLocationController alloc] init];
CLController.delegate = self;
[CLController.locMgr startUpdatingLocation];
}
- (void)locationUpdate:(CLLocation *)location {
locLabel.text = [location description];
}
- (void)locationError:(NSError *)error {
locLabel.text = [error description];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
The above code is placed in a view controller called GetMyLocationViewController, and I have another view controller called MainScreenViewController.
When the screen loads, the MainScreenViewController gets loaded, and I will need the GPS location to continue operations with this screen.
In the ViewDidLoad method of MainScreenViewController I wrote the following;
- (void)viewDidLoad {
[super viewDidLoad];
GetMyLocationViewController *getMyLocationViewController = [[GetMyLocationViewController alloc]initwithXib:nil bundle:nil];
[self.navigationController pushViewController:getMyLocationViewController Animation:YES];
// AND THEN I NEED TO ACCESS THE LONGITUDE AND LATITUDE VALUES
}
When the above code gets executed, the viewDidLoad method of MainScreenViewController gets executed, but not the locationUpdate method. The only way I could get the values of longitude and latitude is by the execution of locationUpdate method. So how can I get these values?
Do you tested in a device? xcode before the version 4.2 dont have a GPS simulator, because of that the method locationUpdate never call.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
if([self.delegate conformsToProtocol:#protocol(CoreLocationControllerDelegate)]) { // Check if the class assigning itself as the delegate conforms to our protocol. If not, the message will go nowhere. Not good.
[self.delegate locationUpdate:newLocation];
}
}
Are you sure you are loading your GetMyLocationViewController? Your code only shows loading the MainScreenViewController, which, in its -viewDidLoad method, loads itself again, which would cause an infinite loop of loading and pushing MainScreenViewControllers.
UPDATE: That CoreLocationController class in the tutorial seems unnecessary. Rather that using it, make CLLocationManager a property of your GetMyLocationViewController. Make GetMyLocationViewController's -viewDidLoad method look like this:
-(void)viewDidLoad {
[super viewDidLoad];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
}
don't forget to import the CoreLocation library and implement the delegate methods.

How do I get CLLLocationManager to return immediately with a location?

I am trying to tweak my CLLocationManager settings so that when I call startUpdatingLocation it calls the delegate right away. Any idea on how to do this?
Thats not possible. The reason is simple, the device has no position fix that matches your desired accuracy the whole time. It might have to turn on the GPS chip to do this which also takes some time to ge a location (without additional infos and an outdated almanac this might take up to 15 minutes in the worst case).
It's not possible to tweak CLLocationManger in that way. However, what you could do is writing a wrapper around CLLocationManager which obtains the current position on startup and returns it everytime you ask for it. However, there might be situations where the location is not (yet) available, and this approach is quite power-consuming as you are using the GPS-device all the time.
This is by far NOT the best way (not even a good way probably) to use a CLLocationManager and when I find the time I am planning on going back and changing my code to only have a single CLLocationManager within my AppDelegate and have all sub view controller call the AppDelegate for their location needs.
I have the following code in a view controller (not the main controller or app delegate either), and about 5-10 seconds after the view controller is pushed by my main views navigation controller, it starts writing to NSLog with an accurate long/lat of my position (i.e. the delegate method locationManager:didUpdateToLocation:fromLocation: starts getting called):
- (void)viewDidLoad {
[super viewDidLoad];
// ....
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
[locationManager setDelegate:self];
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[locationManager setDistanceFilter:kCLDistanceFilterNone];
[locationManager startUpdatingLocation];
// ....
}
// This will be called just a few seconds after the view appears and the
// location is accurate (if I have location services on and good sky-line of sight)
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSDate* eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (abs(howRecent) < 5.0) {
self.latitude = [NSNumber numberWithDouble:newLocation.coordinate.latitude];
self.longitude = [NSNumber numberWithDouble:newLocation.coordinate.longitude];
NSLog(#"New Latitude %+.6f, Longitude %+.6f", newLocation.coordinate.latitude, newLocation.coordinate.longitude);
}
}
// Then, tear down the location Manager (so I can use it in another view controller)
// A bit excessive possibly, but I like to be absolutely sure that my App does not use the GPS
// when it doesn't need it to save batter life.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopUpdatingHeading];
[locationManager stopUpdatingLocation];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopUpdatingHeading];
[locationManager stopUpdatingLocation];
}
- (void)viewDidUnload {
[locationManager stopUpdatingLocation];
[locationManager stopMonitoringSignificantLocationChanges];
[locationManager stopUpdatingHeading];
[locationManager setDelegate:nil];
self.locationManager = nil;
}
- (void)dealloc {
// ....
[locationManager stopUpdatingLocation];
[locationManager setDelegate:nil];
[locationManager release], locationManager = nil;
// ....
[super dealloc];
}
Then, in places like actionSheet:clickedButtonAtIndex:, I can get the location values on0demand with locationManager.location.coordinate.latitude / locationManager.location.coordinate.longitude or self.latitude / self.longitude.

iPhone - Initial user location with CCLocationManager

I'm using that code running on an iPhone 4 :
- (id)init
{
self = [super initWithNibName:#"OfflineView" bundle:nil]; // ok, not perfect but for test, that works fine
if (self) {
self.locationMgr = [[CLLocationManager alloc] init];
self.locationMgr.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
self.locationMgr.distanceFilter = kCLDistanceFilterNone;
self.locationMgr.headingFilter = kCLHeadingFilterNone;
self.locationMgr.delegate = self;
}
return self;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
// do things
}
// triggered when showing the view, first call here after the init
- (void) start
{
self.view.hidden = NO;
[self.locationMgr startUpdatingLocation];
[self.locationMgr startUpdatingHeading];
}
but the delegate method is not triggered.
It's only triggered when the phone moves.
How may I init my process with a valid user location when the view appears, without asking my user to shake it phone and make a 100m run, before being able to do something ?
You could "prime" it by kicking the delegate method yourself once.
- (void) start
{
self.view.hidden = NO;
[self.locationMgr startUpdatingLocation];
[self.locationMgr startUpdatingHeading];
[self locationManager: self.locationMgr didUpdateToLocation: [self.locationMgr currentLocation] fromLocation: nil];
}
where do you call start:? you should be getting first fix without moving. It's an asynchronous call back so might take time.
Ideally you should be calling startUpdateLocation in the init/viewDidLoad and then read it in locationUpdate:
- (void)locationUpdate:(CLLocation *)location {
// Read location
}

stop location updates from the iphone UI?

Is it possible to start / stop location updates from the UI of the iphone? All I need from the app is to show me my location unless I click "stop" and then "start" again.
I can't seem to be able to do that...I have my location displayed properly, and I also created two IBButtons and created a function for each of them, however, my app crashes when I click on each one of those buttons. I placed those functions under the viewcontroller.m.
I am kind of new to this, so any help would be appreciated. Thanks!
(IBAction)startUpdating: (CLLocation *)location
{
[location startUpdatingLocation];
}
(IBAction)stopUpdating: (CLLocation *)location
{
[location stopUpdatingLocation];
}
start/stopUpdatingLocation are CLLocationManager instance methods, rather than CLLocation instance methods... so create a CLLocationManager instance.
.h
#interface someClass:somesuperclass{
CLLocationManager * locationManager;
BOOL updating;
}
-(IBAction)toggleUpdating:(id)sender;
#end
.m somewhere in the view load/ or init cycle:
-(void)viewDidLoad{
[super viewDidLoad];
locationManager = [[CLLocationManager alloc] init];
}
-(void)viewDidUnload{
[locationManager stopUpdatingLocation];
[locationManager release];
[super viewDidUnload];
}
-(IBAction)toggleUpdating:(id)sender
{
if(!updating)
{
[locationManager startUpdatingLocation];
}else{
[locationManager stopUpdatingLocation];
}
updating = !updating;
}
also your action above will never work, because the thing after a colon in an action will be the object that sent the action, a UIButton in your case.

CLLocationManager never calls delegate methods [duplicate]

This question already has answers here:
Why the CLLocationManager delegate is not getting called in iPhone SDK 4.0?
(5 answers)
Closed 7 years ago.
Hi I'm playing around with locations on iPhone and right from the start I ran into problems. I've localized my problem and determined it's CLLocationManager that's bugging me.
So I developed very simple application. I just have a view controller with a CLLocationManager in it. On view did load I initialize CLLocationManager and start updating. I've also implemented two methods didFailWithError and didUpdateToLocation.
I've read a lot of questions and what i have learned so fare is this. You have to retain CLLocationManager during initialization. Also it's wise to set CLLocationManagers delegate to nil during unloading of a view (something to do with messages passing to CLLocationManager because framework retains it and it's never properly release)
All in all I just cant find a decent explanation on what to do and how to make it work.
Here's my code so if anybody could figure it out I would appreciate it.
viewControllers header file
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface CoreLocationViewController : UIViewController <CLLocationManagerDelegate>
{
CLLocationManager *locManager;
}
#property (nonatomic, retain) CLLocationManager *locManager;
#end
viewController .m file
#import "CoreLocationViewController.h"
#implementation CoreLocationViewController
#synthesize locManager;
- (void)viewDidLoad
{
[super viewDidLoad];
self.locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
[self.locManager startUpdatingLocation];
self.view.backgroundColor = [UIColor grayColor];
}
#pragma mark -
#pragma mark Location Delegate
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"in fail with error");
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"in update to location");
}
#pragma mark -
#pragma mark Memory Management
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload
{
self.locManager.delegate = nil;
[super viewDidUnload];
}
- (void)dealloc
{
[locManager release];
[super dealloc];
}
#end
BTW: I'm using iOS 4.2. I'm pointing this out because I read that Apple has changed location delegates in iOS 4
In my case, the delegate never gets called because of threading problem
the thread that operates LocationManager must have an NSRunLoop set
up
if you use main thread, you already have a runloop, now you just
need to instantiate the manager and use it on main thread
Read this to know more about CLLocationManagerDelegate
Furthermore in iOS8 you must have two extra things:
Add a key to your Info.plist and request authorization from the location manager asking it to start.
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
You need to request authorization for the corresponding location method.
[self.locationManager requestWhenInUseAuthorization]
[self.locationManager requestAlwaysAuthorization]
Code example:
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
// Check for iOS 8. Without this guard the code will crash with "unknown selector" on iOS 7.
if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) {
[self.locationManager requestWhenInUseAuthorization];
}
[self.locationManager startUpdatingLocation];
Source: http://nevan.net/2014/09/core-location-manager-changes-in-ios-8/
Other posters have mentioned checking for authorization, and you should definitely add that, but I do not think that is your problem. If you were not authorized your didFailWithError: method would be called. This leads to believe something else is going on here.
It may be a good idea to make sure that your location manager is indeed being allocated and while your at it you can fix your memory leak.
Try this:
CLLocationManager* lm = [[CLLocationManager alloc] init];
NSAssert(lm != nil, #"Failed to allocate CLLocation manage");
self.locManager = lm;
[lm release];
self.locManager.delegate = self;
[self.locManager startUpdatingLocation];
// Let's just print what we got
NSLog(#"CLLocationManager is %#", lm);
If your run this your will get one of the following results:
1) if the location manager is returning nil, your program will crash into the debugger (because of NSAssert)
2) if "CLLocationManager is ...." prints and still you see no updates then you have a real mystery on your hands.
3) Nothing prints. That would mean, perhaps due to something incorrectly linked up in Interface Builder, that viewDidLoad is not being called at all.
4) You will get a didFailWithError: call because you are not authorized. Which means that at least everything is working as expected.
As an aside you had a memory leak because you were assigning the result of an alloc directly to a property with the retain attribute. After alloc your count would be +1 after assignment it would +2, so if the view was unloaded and reloaded you would leak.
Hope some of this helps.
This may be nit picky but in viewDidLoad change:
locManager.delegate = self;
to:
self.locManager.delegate = self;
I don't know if it will fix it, but is best to be consistent.
So I managed to find a bug. It was in memory management. In my app delegate file I initialized a CoreLocationViewController and added it as a subview to my window, after that I released it and all is well. But, the releasing part was not a good way to go. After I released my ViewController, dealloc method gets called and it releases locationManager.
So the proper thing to do is not to release a ViewController that holds locationManager, or find another way of dealing with it. I'm not quite sure why that was a problem because I thought that after adding a ViewController to a window it gets retained, therefore its localtionManager gets retained as well. If anybody can clear it up for me it would be much appreciated.
are you testing on device? are location services enabled?
if (self.locManager.locationServicesEnabled){
//do domething
}
Idk if it is a matter but in my code I also setup the desired accuracy
[locManager setDesiredAccuracy:kCLLocationAccuracyBest];
For example.
Try this:
NSLog(#"location services enabled: %d", [locManager locationServicesEnabled]);
From the docs:
The user can enable or disable location services altogether from the Settings application by toggling the switch in Settings > General > Location Services.
I've got the same problem and as paxx said, it's a memory management problem. My solution is retain 'self' manually. As the code shows below:
- (void) viewDidload
{
[super viewDidload];
if (![CLLocationManager locationServicesEnabled]) {
NSLog(#"Location Service Disabled!");
} else {
[self retain];
//initialize the cllocationmanager obj.
}
}
-(void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)locations
{
//do your surf
[self release];
}
-(void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
//do your stuff
[self release];
}
-(void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error
{
//do your stuff
[self release];
}
Not pretty, but works fine for me.