In my UIViewController's initWithNibNameOrNil, I call:
locationManager = [CLLocationManager new];
to create the object, then later in viewDidAppear I call:
[locationManager startUpdatingLocation];
[locationManager startUpdatingHeading];
But I never receive a location update; the location manager never starts updating the location. Yet if I replace that same line the initWithNibNameOrNil with:
locationManager = [[myAppDelegate appDelegate] locationManager];
everything works great, except for random crashes sometimes when
locationManager.delegate = self;
is set in the very next line after location manager is set to the already allocated manager in the app delegate. None of this makes sense to me; I don't understand why one is different from the other, much less why neither of them works consistently. Can someone please enlighten me?
Summary:
Method 1 (does not work):
In MapView.m:
initWithNibNameOrNil:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self) {
locationManager = [CLLocationManager new];
locationManager.delegate = self;
locationManager.distanceFilter = kCLHeadingFilterNone;
locationManager.headingFilter = 3;
if(([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateCharging) || ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateFull))
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
else
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
//more setup irrelevant to the question
}
return self;
}
viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[locationManager startUpdatingLocation];
[locationManager startUpdatingHeading];
}
Notes: Using this method of creating the location manager, location services never get enabled after startUpdatingLocation is called in viewDidAppear and so no location updates are ever received.
Method 2 (does work, mostly):
In myAppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
self.locationManager = [CLLocationManager new];
self.locationManager.distanceFilter = kCLHeadingFilterNone;
self.locationManager.headingFilter = 3;
if(([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateCharging) || ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateFull))
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
else
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
//more irrelevant setup
}
In MapView.m:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self) {
locationManager = [[Trail_TrackerAppDelegate appDelegate] locationManager];
locationManager.delegate = self;
//more irrelevant setup
}
return self;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[locationManager startUpdatingLocation];
[locationManager startUpdatingHeading];
}
Notes: this allocation method works, except if I push MapView, then pop it, push it again, pop it again, then try to push it again, I get a crash every time at the line in initWithNib... where I set the delegate to self; NSZombieEnabled says:
-[CLLocationManager setDelegate:]: message sent to deallocated instance 0x9540ad0
Interesting...
NSLog(#"%s: %#; %#", PRETTY_FUNCTION, self, locationManager) says this:
-[MapView initWithNibName:bundle:]: <MapView: 0x92ae3e0>; <CLLocationManager: 0x92a3560>
Method 3 (works all-around):
In MapView.m:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
locationManager = [CLLocationManager new];
locationManager.delegate = self;
locationManager.distanceFilter = kCLHeadingFilterNone;
locationManager.headingFilter = 3;
if(([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateCharging) || ([[UIDevice currentDevice] batteryState] == UIDeviceBatteryStateFull))
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
else
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
[locationManager startUpdatingHeading];
}
Notes: all of the allocation and setup is done in viewDidAppear, and I don't get any crashes after repeated pushing/popping of the view controller. This is great, but I don't like allocating in viewDidAppear because the app lags for just a moment because (I think) the allocation of locationManager clogs up the main thread for that time. I just really don't understand why Method 1 doesn't work at all, Method 2 crashes, and Method 3 works. Don't they all do more-or-less the same thing?
Sorry for all the code, and congrats to anyone who made it all the way through this maze of a question! :)
Try:
locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
[self.locationManager startUpdatingLocation];
instead of new. Are you sure you are assigning the delegate as self? Otherwise your class won't listen for location updates.
Turns out the culprit was a seemingly-innocent piece of code that was turning off the location manager as soon as it was being started. The only reason I saw it was because I compared the project to a recent backup, and noticed the difference. Another problem solved because of backing up!
Careful where you're defining your location manager.
When using ARC, it could happen that your location manager instance gets discarded.
Related
I am having ClLocationManager code in my viewWillAppear. I the same view controller I am having a button which opens a web view. I want to stop location manager IMMEDIATELY, when user taps on button. I am using [locationManager stopUpdating] and locationManager.delegate = nil. From CLLOCATION delegate method I open MFMailComposer sheet.
Problem: Even after clicking on button (which is opening web view), my MailComposerCode executes. How to stop it?
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:animated];
[self setUpProgressBar];
if (webViewButtonClicked == YES)//when user came bck from web view pick up latest coordinates
{
webViewButtonClicked = NO;
[self getLocationCoordinates];
}
else//get latest coordinates from CLLocation manger
{
if (!locationManager) {
locationManager = [[CLLocationManager alloc] init];
}
if (!self.geocoder) {
self.geocoder = [[CLGeocoder alloc] init];
}
locationManager.delegate = self;
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// When "tracking" the user, the distance filter can be used to control the frequency with which location measurements
// are delivered by the manager. If the change in distance is less than the filter, a location will not be delivered.
locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters;
// Once configured, the location manager must be "started".
[locationManager startUpdatingLocation];
}
}
- (IBAction)goToWEBVIEW
{
NSLog(#"setting to YES");
webViewButtonClicked = YES;
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
aWebViewController = [[WebViewController alloc] initWithNibName:#"WebViewController" bundle:nil];
[self.navigationController aWebViewController animated:NO];
}
CLLocationManager delegate method:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
if (webViewButtonClicked==NO)
{
NSLog(#"1234");
if (!self.sender) {
[self.gpsActivityindicator stopAnimating];
[self stopUpdatingLocation:#""];
self.destinationForProgressView = .25;
[NSThread detachNewThreadSelector:#selector(startTheBackgroundJob) toTarget:self withObject:nil];
**[self openMailComposer];**
}
}
}
So you can see in goToWebView method I am having a flag webViewButtonClicked = YES, but delegate method is called before the user has tapped on web view button. So condition if (webViewButtonClicked==NO) becomes true? How can I stop this scenario?
Thanks.
- (IBAction)goToWEBVIEW
{
[locationManager stopUpdatingLocation];
NSLog(#"setting to YES");
webViewButtonClicked = YES;
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
aWebViewController = [[WebViewController alloc] initWithNibName:#"WebViewController" bundle:nil];
[self.navigationController aWebViewController animated:NO];}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
[locationManager stopUpdatingLocation];
if (webViewButtonClicked==NO)
{
NSLog(#"1234");
if (!self.sender) {
[self.gpsActivityindicator stopAnimating];
[self stopUpdatingLocation:#""];
self.destinationForProgressView = .25;
[NSThread detachNewThreadSelector:#selector(startTheBackgroundJob) toTarget:self withObject:nil];
**[self openMailComposer];**
}
}
}
I converted my app to ARC and removed all the pre-build release errors. It launches, but will crash (EXC_BAD_ACCESS) as soon as I call any method (all of which are attached to UIButtons). I also noticed that it will ask if the user will allow for the app to use the user's location, but the alert will disappear before the user can answer yes or no.
I feel like there's some very basic setting I'm missing causing this.
Here's the first method called, it won't let the user actually say if they'll allow location services. The alert fires then disappears. Does this help anyone's diagnosis?
-(void)startLocation
{
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
}
Also, here's my didFinishLaunchingWithOptions method
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *nav = [[UINavigationController alloc] init];
StartPageViewController *start = [[StartPageViewController alloc]init];
NSManagedObjectContext *context = [self managedObjectContext];
if (!context)
{
// Handle the error.
}
start.managedObjectContext = context;
nav.viewControllers = [NSArray arrayWithObjects:start, nil];
[_window addSubview:[nav view]];
[self.window makeKeyAndVisible];
return YES;
}
Try retaining your navigation controller by making it a strong property on your delegate.
At the moment, I don't see any code that would cause ARC not to release nav at the end of the method. That would release start, which would release context.
All I needed to change (so far) was:
self.window.rootViewController = nav;
instead of:
[_window addSubview:[nav view]];
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.
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
Why is this CLLocationManager inside my singleton not working? I took this code http://jinru.wordpress.com/2010/08/15/singletons-in-objective-c-an-example-of-cllocationmanager/
Not altertering his code at all (so if I should add something to his code let me know) its my first singleton ever.
- (CLLocationManager *)locationManager {
if (locationManager != nil) {
return [LocationController sharedInstance].locationManager;
}
self.locationManager = [LocationController sharedInstance];
[LocationController sharedInstance].locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters;
return [LocationController sharedInstance].locationManager;
}
(void)viewDidLoad
{
[super viewDidLoad];
// Start the location manager.
[LocationController sharedInstance].delegate = self;
//[[self locationManager] startUpdatingLocation];
[[LocationController sharedInstance].locationManager startUpdatingLocation];
I don't quite understand what you want to achieve in your - (CLLocationManager *)locationManager method here. But in the init function in LocationController.h, you should add something like this:
- (id)init
{
self = [super init];
if (self != nil) {
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
return self;
}
And when you want to call the singleton object in other controller to update the location, you should be able to just call:
[[LocationController sharedInstance].locationManager startUpdatingLocation];
Remember to also implement the delegate method - (void)locationUpdate:(CLLocation*)location in your controller.
Hope it helps this time if my post didn't.
Perhaps the problem is in the delegate. This points to the file, not the singleton.
Assuming that - (CLLocationManager *)locationManager is a method of LocationController the line
self.locationManager = [LocationController sharedInstance];
makes no sense as you assign the singleton instance of LocationController to self.locationManager which is of type CLLocationManager.
Here is what I did and you can find the complete example on github.
https://github.com/irfanlone/CLLocationManager-Singleton-Swift