Receiving CLLocation updates on a background thread - iphone

I am trying to implement a (non-concurrent) NSOperation for location updates using the iPhone SDK. The "meat" of the NSOperation subclass goes something like this:
- (void)start {
// background thread set up by the NSOperationQueue
assert(![NSThread isMainThread]);
if ([self isCancelled]) {
return;
}
self->locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = self->desiredAccuracy;
locationManager.distanceFilter = self->filter;
[locationManager startUpdatingLocation];
[self willChangeValueForKey:#"isExecuting"];
self->acquiringLocation = YES;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)cancel {
if ( ! self->cancelled ) {
[self willChangeValueForKey:#"isCancelled"];
self->cancelled = YES;
[self didChangeValueForKey:#"isCancelled"];
[self stopUpdatingLocation];
}
}
- (BOOL)isExecuting {
return self->acquiringLocation == YES;
}
- (BOOL)isConcurrent {
return NO;
}
- (BOOL)isFinished {
return self->acquiringLocation == NO;
}
- (BOOL)isCancelled {
return self->cancelled;
}
- (void)stopUpdatingLocation {
if (self->acquiringLocation) {
[locationManager stopUpdatingLocation];
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self->acquiringLocation = NO;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
locationManager.delegate = nil;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
assert(![NSThread isMainThread]);
// ... I omitted the rest of the code from this post
[self stopUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)theError {
assert(![NSThread isMainThread]);
// ... I omitted the rest of the code from this post
}
Now, on the main thread I create an instance of this operation and add it to an NSOperationQueue. The start method gets called, however none of the -locationManager:... delegate methods get called. I don't get it why they never get called.
I did make the interface adhere to the <CLLocationManagerDelegate> protocol. I'm letting the NSOperationQueue manage the thread for this operation, so it should all be conforming to the CLLocationManagerDelegate documentation:
The methods of your delegate object are called from the thread in which you started the corresponding location services. That thread must itself have an active run loop, like the one found in your application’s main thread.
I am not sure what else to try for this to work. Maybe it's staring me in the face... Any help is appreciated.
Thanks in advance!

You are missing the "active run loop" part. At the end of your start method add: while (![self isCancelled])
[[NSRunLoop currentRunLoop] runUntilDate:someDate];

Related

How to stop CLLocation manager updating IMMEDIATELY

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];**
}
}
}

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.

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
}

CLLocationManager crashing on release, but why?

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

loadview ONLY when I get my GPS location

I have been stuck on this for days, and was wondering if anyone had any clues? Should be simple, but it has me stuck! I get my location, then continue. But I want to stay IN THAT METHOD - LOOPING - until I get a valid location. Then loadview. THANKS for any tips!
I am using the standard:
- (id)init
{
if (self = [super init])
{
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
self.locationManager.delegate = self; // send loc updates to myself
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[self.locationManager startUpdatingLocation];
}
return self;
}
- (void)viewDidLoad
{
// do my processing here ONLY when I get a valid location***************************
// and if I never get a valid location, then just go to my last location.
}
(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSDate* eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (abs(howRecent) < 5.0)
{
[manager stopUpdatingLocation]
printf("latitude %+.6f, longitude %+.6f\n", newLocation.coordinate.latitude, newLocation.coordinate.longitude);
}
}
Rather than spinning in your viewDidLoad, how about putting up a temporary view until you have your GPS location?
// custom full-screen view class of your choice
// could just be a UIImageView if you wanted
SplashOverlay *splash;
- (void)viewDidLoad {
[super viewDidLoad];
splash = [[SplashOverlay alloc] initWithNibName:#"SplashOverlay" bundle:nil];
[self.view addSubview:splash.view];
}
// do this code to get rid of the view
- (void) doneWithSplashScreen {
[splash.view removeFromSuperview];
[splash release];
splash = nil;
}
your view will still be under the splash screen waiting, but nobody can interact with it until you're ready.