CLLocationManager is alive even when not needed - ios

I have a mapping functionality in my app. It belongs to one of the Tab Bar controller tabs.
The problem I have is that the app is asking for location permissions right after launch.
I dont want to bother the user with location related questions until he actually goes to that map part of the app.
If I understand properly, location manager should not bother to ask for that until I instantiate it. Also, as soon as the user leaves that map part of the app, either by selecting other tabs or by pressing home button..I nil the location manager.
Why is it then asking for that so soon? My question is whether there is some special rule in this particular case...or it is caused by my flawed app design?

I'd go with the "flowed app design" one. You don't showed any code, so it's hard to say, but UITabBarController instantiates all its View Controllers at once, so your CCLocation class is probably being initialized with your TabBar.
What you could do is: initialize your CCLocation stuff only on the -viewWillAppear method on the View Controller that you actually use it.

You can write something like this.
h:
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#interface LocationController : NSObject <CLLocationManagerDelegate> {
CLLocationManager *locationManager;
CLLocation *currentLocation;
}
+ (LocationController *)sharedInstance;
- (void)start;
- (void)stop;
#property (nonatomic, strong) CLLocation *currentLocation;
#end
m:
#import "LocationController.h"
#implementation LocationController
static LocationController *sharedInstance;
+ (LocationController *)sharedInstance {
static LocationController *sharedClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClient = [[LocationController alloc] init];
});
return sharedClient;
}
-(id) init {
if (self = [super init]) {
currentLocation = [[CLLocation alloc] init];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[self start];
}
return self;
}
- (void)start {
[locationManager startUpdatingLocation];
}
- (void)stop {
[locationManager stopUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *loc = [locations lastObject];
if ( abs([loc.timestamp timeIntervalSinceDate: [NSDate date]]) < 120) {
self.currentLocation = loc;
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"%#", error);
}
#end
Then whenever you need to locate you can do the following:
[[LocationController sharedInstance] start];

Where are you instantiating the CLLocationManager? While all the view controllers are instantiated when the tab bar controller is instantiated, their respective views are not created until you go to the relevant tab. So, if you're instantiating your CLLocationManager in one of the controller's init methods, then that location manager will be created when the controllers are (i.e. when the tab bar controller is instantiated). If you have the instantiation of the location manager in viewDidLoad, though, it shouldn't be instantiated until you click on the appropriate tab.

Related

Cannot get the my location dot for Google Maps iOS SDK

Currently, I am not able to get my location in the app I am developing.
Basically, in the documentation for Google Maps iOS SDK, Google mentions that you can:
enable the blue "My Location" dot and compass direction by setting
myLocationEnabled on GMSMapView
https://developers.google.com/maps/documentation/ios/map#my_location
However, when I do that, the dot does not appear in my view.
Here's my setup, I have a view controller that contains a view. This view contains three things, two buttons and a map view:
I have linked my mapView with GMSMapView and I can see the map without any problems.
Now, what I would want, is to be located.
I tried two things. First, using a custom, draft button (the i for information), I tried to manually set the location to the mapView but this wasn't working even though the locationServicesEnabled method returned YES.
Then, I tried using GoogleMaps's dot but this isn't working either.
Here's my code:
StudyDisplayViewController.h:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import "GoogleMaps/GoogleMaps.h"
#interface StudyDisplayViewController : UIViewController <CLLocationManagerDelegate> {
}
#property (strong, readwrite) CLLocationManager *locationManager;
#property (weak, nonatomic) IBOutlet GMSMapView *mapView;
#end
StudyDisplayViewController.m
#import "StudyDisplayViewController.h"
#interface StudyDisplayViewController ()
- (IBAction)closeStudyDisplay:(id)sender;
- (IBAction)locateMe:(id)sender;
#end
#implementation StudyDisplayViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
NSLog(#"Starting the location service");
[self.locationManager requestWhenInUseAuthorization];
[self.locationManager startMonitoringSignificantLocationChanges];
if([CLLocationManager locationServicesEnabled]) {
NSLog(#"all good");
}
else {
NSLog(#"Damn son");
}
}
self.mapView.settings.compassButton = YES;
self.mapView.myLocationEnabled = YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void) locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
}
-(void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSLog(#"Getting Location");
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (IBAction)closeStudyDisplay:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)locateMe:(id)sender {
NSLog(#"User's location: %#", self.mapView.myLocation);
}
I am using Xcode 6.3.2 and iOS 8.3.
First check out following things are there or not.
locationManager = [[CLLocationManager alloc]init];
locationManager.delegate= self;
if([locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]){
[locationManager requestWhenInUseAuthorization];
}
[locationManager startUpdatingLocation];
_mapView.myLocationEnabled = YES;
_mapView.settings.myLocationButton = YES;
If Everything is there and if you are testing on Simulator then try to change the location in Simulator from debug Tab, e.g. change It to Free car run of any of them.
Also, check if you have added Description in PLIST for NSLocationWhenInUseUsageDescription.

Where to store app location data?

I have an app that needs to retrieve the longitude/latitude coordinates of the device when it's opened, and then communicate with the server using these coordinates. My app is based on a UITabBarController so has different pages, some of which need to use this location data. I also need to implement pull to refresh on some of these pages.
What I need to know is: Where should I store the location coordinates and where should I implement the CLLocationManager? Should I have a location manager inside each class that needs location, or should it be in the app delegate?
Thanks
Just create a singleton location manager class (say, MyLocationManager). Your .h file can look like this:
#interface MyLocationManager : NSObject <CLLocationManagerDelegate>
#property (strong, nonatomic) CLLocation *currentLocation;
+ (id)sharedInstance;
- (void)updateCurrentLocation;
#end
Then in the .m file implement the methods:
#import "MyLocationManager.h"
#interface MyLocationManager ()
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
#implementation MyLocationManager
#pragma mark - Singleton methods
+ (id)sharedInstance {
static dispatch_once_t token;
static id shared = nil;
dispatch_once(&token, ^{
shared = [[super alloc] initUniqueInstance];
});
return shared;
}
- (id)initUniqueInstance {
self = [super init];
if (self) {
self.locationManager = [[CLLocationManager alloc] init];
[self.locationManager setDesiredAccuracy:kCLLocationAccuracyKilometer];
self.locationManager.delegate = self;
}
return self;
}
#pragma mark - Public methods
- (void)updateCurrentLocation {
[self.locationManager startUpdatingLocation];
}
#end
Implement the delegate methods and update the currentLocation property when you get the updated value. I'd also advice to check for geolocation permissions somewhere and react appropriately. And keep in mind that you should use notifications (NSNotificationCenter) to inform other objects about updates to singleton's properties, not delegation (generally, you shouldn't tie your singleton to any object). Good luck!

Using a class (not an instance) as an iOS delegate, CLLocationManager delegate callbacks aren't called

Related: Can I use a class method as a delegate callback?
I'm trying to use a static class, MyClass, as a delegate for its own static CLLocationManager member, but the CLLocationManager delegate methods I've implemented aren't being called. I've set the delegate as [myClass class], properly implemented the delegate methods, and included the protocol in MyClass.h.
MyClass.h
#interface iOSSonic : NSObject <CLLocationManagerDelegate>
MyClass.m
locationManager declaration:
#implementation myClasss : NSObject
...
static CLLocationManager *locationManager = nil;
I'm lazily instantiating the static CLLocationManager via the follow method:
+(CLLocationManager*)getLocationManager {
if (locationManager == nil) {
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = [myClass class]; // we set the delegate of locationManager to self.
locationManager.desiredAccuracy = kCLLocationAccuracyBest; // setting the accuracy
locationManager.distanceFilter = 0.5; // get updates for location changes > 0.5 m
[locationManager requestWhenInUseAuthorization];
}
return locationManager;
}
...and then from my ViewController calling the following MyClass method:
+(void)myFunction {
[self.getLocationManager startUpdatingLocation];
}
Delegate method implementations:
...
+(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
...
}
+(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
...
}
ViewController.m
// no initialization needed for static myClass
- (IBAction)onButtonClick:(id)sender {
[myClass myFunc] // This should trigger the didUpdateLocations delegate method, but it doesn't
To ensure that this wasn't some problem related to having the delegate be a static (non-instantiable) class and the delegate callbacks be class methods, I also tried with locationManager as a #property rather than a static member, and created an instance of myClass, setting myClass's locationManager's delegate to self. I also replaced getLocationManager with an overridden locationManager getter, and changed the delegate callbacks to instance methods.
MyClass.m
Initialization:
-(id)init {
if (self = [super init]) {
// do nothing
}
return self;
}
LocationManager declaration and instantiation:
...
#interface MyClass()
#property (strong, nonatomic) CLLocationManager *locationManager;
#end
#implementation
...
// Lazily instantiate locationManager
-(CLLocationManager*)locationManager {
if (!_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self; // we set the delegate of locationManager to self.
_locationManager.desiredAccuracy = kCLLocationAccuracyBest; // setting the accuracy
_locationManager.distanceFilter = 0.5; // get updates for location changes > 0.5 m
[_locationManager requestWhenInUseAuthorization];
}
return _locationManager;
}
Delegate method implementations:
...
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
...
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
...
}
ViewContoller.h
...
#property (strong, nonatomic) myClass *myClassInstance;
...
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
...
self.myClassInstance = [[myClass alloc] init];
What am I doing wrong?
It was a stupid location services permissions thing. Turns out it had nothing to do with static members, classes vs. instances, etc. This fixed it for me: https://stackoverflow.com/a/25765345/1402368
Of course it was something stupid like this...
Here's my understanding of what's going on:
Instance method calls and class method calls are semantically different in Objective-C, and not interchangeable.
The method declarations:
+(void)someMethod;
and
-(void)someMethod;
Define 2 different kinds of methods. To call them, you have to know if you're calling an instance method or a class method, and code accordingly.
The location manager is written to call INSTANCE methods on it's delegate, not class methods.
Thus, you can't do what you're trying to do (make a CLASS a delegate instead of an instance of a class.)
You might be able to design your own custom class who's objects expect to have a class set as their delegate, but then you would only ever be able to assign a class as the delegate, never an instance of that class.

NSLog of CLLocation does not print at launch, only when button is pressed

My app display the user's last known/current coordinates in a text label when a button is pressed.
I set up a block of code in my main file to print the user's latitude coordinate to the log, but it does not print anything to the log when I run the app.
Why won't the NSLog print to the console?
Here is the snippet of code that is supposed to be printing the location to the log once the app launches and the user allows the app to access their location:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
CLLocation * currentLocation = [locations lastObject];
NSLog(#"%f", currentLocation.coordinate.latitude);
Below is my full ViewController.h code:
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#interface ViewController : UIViewController <CLLocationManagerDelegate>
#property (nonatomic, strong) IBOutlet UILabel * gpsLabel;
#property (nonatomic, strong) CLLocationManager * gpsLM;
-(IBAction)gpsButton;
#end
And here is my full ViewController.m code:
#import "ViewController.h" //This imports the all of the code we have typed in the ViewController.h file.
#import <CoreLocation/CoreLocation.h> //This imports the CoreLocation framework needed for location apps.
//This assigns the Location Manager's delegate to this view controller
#interface ViewController () <CLLocationManagerDelegate>
//This tells the delegate that new location data is available. Manager is the object that updates the event, and the locations object is where the array of location data is stored.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//This allocates memory for and initializes the gpsLM object we setup in ViewController.h
//This means that we can now use the object and do things with it.
self.gpsLM = [[CLLocationManager alloc]init];
//This calls a startUpdatingLocation method for our CLLocationManager object called gpsLM.
//Because this is all in viewDidLoad, it all gets executed right away as soon as the app is opened.
[self.gpsLM startUpdatingLocation];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//This executes the instance method that we declared above in the header.
//Now we are actually implementing the method and can tell it what we want it to do.
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
//This creates an object called currentLocation and sets it's value to whatever the last value is in the locations array.
//Notice how it is also calling a method of lastObject for the object called locations.
//So remember that you can set variables and objects equal to the result of a method call.
CLLocation * currentLocation = [locations lastObject];
//This prints out text to the debug console that states the latitude coordinate of the user's iPhone.
NSLog(#"%f", currentLocation.coordinate.latitude);
}
-(IBAction)gpsButton{
CLLocation * currentLocation = self.gpsLM.location;
self.gpsLabel.text = [NSString stringWithFormat:#"Your Location is %#", currentLocation];
}
#end
It seems that you forgot to assign the location manager delegate:
self.gpsLM = [[CLLocationManager alloc]init];
self.gpsLM.delegate = self; // <-- ADD THIS
[self.gpsLM startUpdatingLocation];
Without this assignment, the location manager doesn't know what object to give the location update to. The method locationManager:didUpdateLocations: never runs.

have object in custom class be seen in my viewcontroller, trying to create locationManager with CLGeocoder

Good Afternoon All,
I am trying to complete the tutorials on locationManager and CLGeocoder but am having trouble understanding how these can interact with one another through classes.
To explain, I followed a tutorial to show current location which works. Now I am trying to have CLGeocoder return information on that location, but I am having trouble since locationManager is in a different class. My problem is understanding custom classes and how view controller ties it all together.
Here is my code so far.
appdelegate.h
#import <UIKit/UIKit.h>
#import "LocationGetter.h"
#import <UIKit/UIKit.h>
#import "LocationGetter.h"
#class ViewController;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (retain, nonatomic) UIWindow *window;
#property (retain, nonatomic) ViewController *viewController;
#property (nonatomic, strong) CLLocation *lastKnownLocation;
#end
appdelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
self.locationGetter = [[LocationGetter alloc] init];
self.locationGetter.delegate = self;
[self.locationGetter startUpdates];
class locationgetter.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#protocol LocationGetterDelegate <NSObject>
#required
- (void) newPhysicalLocation:(CLLocation *)location;
#end
#interface LocationGetter : NSObject
-(void)startUpdates;
#property (nonatomic, weak) id<LocationGetterDelegate>delegate;
#end
locationgetter.m
#import "LocationGetter.h"
#import <CoreLocation/CoreLocation.h>
#interface LocationGetter () <CLLocationManagerDelegate>
#property (nonatomic, strong) CLLocationManager *locationManager;
#end
#implementation LocationGetter
//#synthesize geoCoder;
bool didUpdate = NO;
-(void) startUpdates{
NSLog(#"Starting Location Updates");
if (self.locationManager == nil)
self.locationManager = [[CLLocationManager alloc]init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
[self.locationManager startUpdatingLocation];
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Your location could not be determined." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}
// Delegate method from the CLLocationManagerDelegate protocol.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
if (didUpdate)
return;
didUpdate = YES;
// Disable future updates to save power.
[manager stopUpdatingLocation];
// let our delegate know we're done
[_delegate newPhysicalLocation:newLocation];
}
#end
Now here is the problem...
somewhere I need to put in an CLGeocoder property, and an action from a button that launches the CLGeocoder.
I tried putting this in view controller
#property (strong, nonatomic) IBOutlet CLGeocoder *geoCoder;
- (IBAction)geoCodeLocation:(id)sender;
and this in viewcontroller.m
- (IBAction)geoCodeLocation:(id)sender {
NSLog(#"hello %#,", sender);
[self.geoCoder reverseGeocodeLocation: locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) {
}
but the way I have it, locationManager is not found. I guess because location is found in AppDelegate.M. This is my problem, I do not understand how I can have location to be seen once I'm in the viewcontroller. I come from a c# background where I would just pass a variable, or make a global variable.
Can someone please explain the best way to communication the objects in IOS.
Please let me know if I need to explain myself better.
THanks!!!
I don't know what your requirements are, but if you only need to do location stuff in one view controller in your app, I'd dispense with the LocationGetter and LocationGetterDelegate classes and have the CLLocationManager as an ivar of ViewController and just implement the CLLocationManagerDelegate methods in ViewController.
If you do it this way you eliminate the problem of getting access to the CLLocationManager in the app delegate, so you can do:
[self.geocoder reverseGeocodeLocation: self.locationManager.location completionHandler:
^(NSArray *placemarks, NSError *error) { //completion stuff
}];
Apple do this in their 'GeocoderDemo' demo project, available here - in fact they have a different CLLocationManager instance in each tab VC! (3 instances). I understand that your way is slightly cleaner and more elegant- but for the average app it's probably overkill. Simpler is generally better.
That said, if you absolutely must have the CLLocationManager in the app delegate, wrapped in your custom classes, you could change your method in ViewController.m to
- (IBAction)geoCodeLocation:(id)sender
{
AppDelegate *delegate = [[UIApplication sharedApplication]delegate];
//probably want some error handling here to deal with possibility that
//delegate.lastKnownLocation is nil.
[self.geoCoder reverseGeocodeLocation: delegate.lastKnownLocation completionHandler:
^(NSArray *placemarks, NSError *error) {
//completion stuff
}];
}
(You'll obviously need to #import "AppDelegate.h" in any class you do this in).
Minor points:
-There's no point in making the CLGeocoder an IBOutlet, since it isn't a UI element.
-You are #import ing UIKit and LocationGetter twice in your app delegate.(I realize this might be a typo!).

Resources