After reading some recommendations on stack overflow and some other articles I have implemented a singleton class that allows me to call a method to update a user's current location. To avoid continuous update of the location information, I added a StopUpdatingPosition call. This works fine for the first time, however following calls fail.
I believe this is because of the way the geolocation stuff is initiated in the examples provided where it checks to see if the singleton is already in place(?)
If a singleton is initialised does it remain in memory? On subsequent calls can I check to see if this is the case and just request it to start updating position?
Here is what I have tried so far.
- (id)init {
self = [super init];
if(self) {
self.locationManager = [CLLocationManager new];
[self.locationManager setDelegate:self];
[self.locationManager setDistanceFilter:kCLDistanceFilterNone];
[self.locationManager setHeadingFilter:kCLHeadingFilterNone];
[self.locationManager startUpdatingLocation];
geocoder = [[CLGeocoder alloc] init];
}
return self;
}
+ (LocationFunction*)sharedSingleton {
static LocationFunction* sharedSingleton;
if(!sharedSingleton) {
#synchronized(sharedSingleton) {
sharedSingleton = [LocationFunction new];
}
}
return sharedSingleton;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
NSLog(#"didUpdateToLocation: %#", newLocation);
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
myLat = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude];
myLong = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude];
}
// Store the Long & Lat to global variables here
NSLog(#"Resolving the Address");
} else {
// Handle location lookup fail here
}
} ];
[manager stopUpdatingLocation];
}
I am looking for a way of calling method in an external class from different view controllers. This method obtains the user's current position (Long & Lat), records the info in a global variable, stops any requests to update location method and notifies the view controller when it is done.
Pointers appreciated.
Another stackoverflow question has an answer that provides a solution. Note however that the call is not correct (I am unable to comment on the answer - too new to stackoverflow!). It should reference "sharedManager" :
- (void)viewDidLoad
{
[super viewDidLoad];
[[MyClass sharedManager] startCapture];
}
Hope this helps anyone searching.
Related
How to fetch the location for only one time in ios app.
After getting your location, stop update location manager and also release locationManager because you don't need locationManager anymore.
[self.locationManager stopUpdatingLocation];
self.locationManager.delegate = nil;
self.locationManager = nil;
In ios 9 and above we have a method [locationManagerInstance requestLocation]. But this will take almost ten secs to call back the delegate methods since the location is latest and best.
In another way (for earlier ios9 versions), you could still use the old method [locationManagerInstance startUpdatingLocation] to get the immediate location and also you could validate the timestamp for the best accuracy.
Here is the code that I'm using for getting the location. Create a global reference for locationManager instance.
#interface ViewController ()
{
CLLocationManager *locationManager;
}
Implement these utility methods in your.m file
-(BOOL)canUseLocationManager{
if(([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse) || ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways)){
return YES;
}
return NO;
}
-(void) getCurrentLocation {
locationManager.delegate = self;
// ios 9 and above versions only
//[locationManager requestLocation]; // This may take more time when compare to alternate method
// ios 2 and later versions can use this method
[locationManager startUpdatingLocation];
}
-(void) stopGettingCurrentLocation {
[locationManager stopUpdatingLocation];
}
Implement location service delegates
-(void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
if([CLLocationManager authorizationStatus] != kCLAuthorizationStatusNotDetermined){
[self doneCheckingAccessStatusForLocation];
}
}
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
[manager stopUpdatingLocation];
locationManager.delegate = nil;
/* Clean the locationManager instance if you don't need*/
//locationManager = nil;
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{
[manager stopUpdatingLocation];
locationManager.delegate = nil;
CLLocation* location = [locations lastObject];
NSDate* eventDate = location.timestamp;
/* Implement your business logics here */
/* Clean the locationManager instance if you don't need*/
//locationManager = nil;
}
Final methods to create the location manager instance and initiate fetching location
-(void)checkForLocationService{
if(!locationManager){
locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
locationManager.delegate = self;
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){
[locationManager requestWhenInUseAuthorization];
}else{
[self doneCheckingAccessStatusForLocation];
}
}
-(void)doneCheckingAccessStatusForLocation{
if([self canUseLocationManager]){
[self getCurrentLocation];
}
}
call the checkForLocationService method and implement your logics on the success and failure delegate methods.
[self checkForLocationService];
I'm building an iOS 8 app that uses GeoFencing. I'm having some problems that I will explain later. But first, this is how I create a region and monitor it:
Variables in header file:
#property (nonatomic, strong) CLLocationManager *locationManager;
viewDidLoad:
// Initialize locationManager
self.locationManager = [[CLLocationManager alloc] init];
[self.locationManager requestAlwaysAuthorization];
// Set the delegate
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = kCLLocationAccuracyBest;
Creating a region:
- (CLRegion*)mapDictionaryToRegion:(NSDictionary*)dictionary
{
NSString *title = [dictionary valueForKey:#"title"];
CLLocationDegrees latitude = [[dictionary valueForKey:#"latitude"] doubleValue];
CLLocationDegrees longitude =[[dictionary valueForKey:#"longitude"] doubleValue];
CLLocationCoordinate2D centerCoordinate = CLLocationCoordinate2DMake(latitude, longitude);
CLLocationDistance regionRadius = [[dictionary valueForKey:#"radius"] doubleValue];
return [[CLRegion alloc] initCircularRegionWithCenter:centerCoordinate
radius:regionRadius
identifier:title];
}
Start monitoring it:
-(void)startMonitoringForDictionary:(NSDictionary *)dictionary
{
// Start monitoring for the supplied data
CLRegion *region = [self mapDictionaryToRegion:dictionary];
[self.locationManager startMonitoringForRegion:region];
}
Listening for region crossing:
-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSLog(#"Error: %#", error);
}
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
NSLog(#"didEnterRegion");
}
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
NSLog(#"exited region");
}
I've remembered these keys in my target info:
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
Problem:
Sometimes when I enter a region, it works flawless. Both didEnterRegion and didExitRegion is called like expected. But it appears as if I add the region while inside it, and then exit - it won't call didExitRegion. Only if it calls didEnterRegion first.
What I'm asking:
Could someone please explain the "rules" for region monitoring and how the device manages it, reports it, wakes app, etc? Also, I read in the documentation that I shouldn't expect updates more frequently than every 5 minutes. (This thing I'm making to practice calculates how long you've spent inside a region) - what if the user enters then exists quickly - then didExitRegion will never be called until I enter again?
Thanks!
Erik
You must call requestState immediately when calling startMonitoringForRegion
I need to know if an instance of CLLocationManager (in this case called gps) is updating location calling the proper method
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
In my case the complete method is:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
//NSLog(#"didUpdateToLocation: %#", newLocation);
currentLocation = newLocation;
if (currentLocation != nil) {
longitude.text = [NSString stringWithFormat:#"%.3f", currentLocation.coordinate.longitude];
latitude.text = [NSString stringWithFormat:#"%.3f", currentLocation.coordinate.latitude];
}
address.text = NULL;
[activityIndicator setHidden:NO];
[activityIndicator startAnimating];
NSLog(#"Resolving the Address");
[geocoder reverseGeocodeLocation:currentLocation completionHandler:^(NSArray *placemarks, NSError *error) {
//NSLog(#"Found placemarks: %#, error: %#", placemarks, error);
if (error == nil && [placemarks count] > 0) {
placemark = [placemarks lastObject];
[address sizeToFit];
address.text = [NSString stringWithFormat:#"%#, %#\n%# %#\n%#",
[self sanitizedDescription:placemark.thoroughfare],
[self sanitizedDescription:placemark.subThoroughfare],
[self sanitizedDescription:placemark.postalCode],
[self sanitizedDescription:placemark.locality],
[self sanitizedDescription:placemark.country]];
if (address.text != NULL)
{
gpsTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:gps
selector:#selector(startUpdatingLocation)
userInfo:nil
repeats:NO];
[address sizeToFit];
[gps stopUpdatingLocation];
}
} else {
NSLog(#"%#", error.debugDescription);
}
} ];
}
How can I do?
Thanks in advance :)
subclass your CLLocationManager and add a BOOL that is set to yes when your delegate method gets called...
You can also simply tell your location manager to stop within the delegate method
Below is a simple example code to show how to deal with this issue.
import Foundation
import CoreLocation
public class LocationManager : NSObject, CLLocationManagerDelegate{
var locationManager: CLLocationManager?
public var isLocationUpdate: Bool = false
override public init() {
super.init()
self.locationManager = CLLocationManager()
self.locationManager!.delegate = self
if #available(iOS 9.0, *) {
self.locationManager!.requestWhenInUseAuthorization()
}
self.locationManager!.startUpdatingLocation()
self.isLocationUpdate = false
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.isLocationUpdate = true
print(locations)
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
//Retrieving the error code.
if let code = (error as NSError).code{
isLocationUpdate = false
}
}
Apple deprecated one of the primary location manager delegate methods in iOS 6, so if you are supporting older versions of iOS as well as the current version, you will need to include support for both delegate methods:
iOS 2 - iOS 5:
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
iOS 6 and later:
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
There really is no way for you to check "is my delegate 'active' for this locationmanager?".
What you need to do is keep track yourself of whether you're done with the location updates.
I'm not sure if this is what you need, but here's what i do:
1- when i start requesting updates, i do as you do, i.e. through requestlocationupdates and a delegate.
2- In the case where i don't get the updates i need, i want to time out after some time, so that my app doesn't wait for a location forever, in this case i use a Selector with a timeout:
[self performSelector:#selector(fetchLocationTimeout) withObject:nil afterDelay:LOCATION_LISTEN_TIME];
and in the selector:
(void)fetchLocationTimeout{
if(!finished){
[self stopUpdatingLocation];
[delegate locationFinished:bestEffortAtLocation];//see more on this below
}
}
3- in my didupdate-delegate i both store away the "best" location i have gotten, and if i decide that the new location is "good enough", i finish my location-fetching process:
(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
if(newLocation is the best i have){
self.bestEffortAtLocation = newLocation;
}
// test the measurement to see if it is more accurate than the previous measurement
if (bestEffortAtLocation == nil || newLocation is the best i have) {
// store the location as the "best effort"
if (bestEffortAtLocation is good enough and i want to use it) {
[self stopUpdatingLocation];
// we can also cancel our previous performSelector:withObject:afterDelay: - it's no longer necessary
SEL selector = NSSelectorFromString(#"fetchLocationTimeout");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:nil];
[delegate locationFinished:bestEffortAtLocation];
}
}
}
Finally, this is my method which is called above, both from within the didupdate-delegate and from the selector timeout to reduce the risk of a race condition:
(void)stopUpdatingLocation{
finished = true;
NSLog(#"stopUpdatingLocation! manager: %#", [locationManager description]);
[locationManager stopUpdatingLocation];
locationManager.delegate = nil;
}
Final note: the "delegate" i'm calling when i'm done is my own interface describing a class that knows how to take care of the result of a location-fetching process. it has only one method to be implemented "locationFinished", as you can see.
Hope this helps even though my code-pasting skills aren't exactly ninja.
I'm having an issue where my app will not fire the didEnterRegion event if I start the app within the region. If I start the app outside the region and then enter the region, it fires. If I start the app inside the region, then leave the region, then re-enter the region, it fires.
Any suggestions on how to get it to fire as soon as the app is opened if it's in the region would be much appreciated!
I suggest you to use this code
[locationManager requestStateForRegion:region];
And use the delegate method didDetermineState: to check if the state is CLRegionStateInside or CLRegionStateOutside.
I don't think you can do that.
But, you can get the current location and check if it's inside the region you're specifying yourself. CLCircularRegion has a containsCoordinate: method for this.
The first conclusion is that didEnterRegion is implemented consistently with its name. :)
Implement something like this in your CLLocationManagerDelegate:
- (void) locationManager: (CLLocationManager *) manager
didStartMonitoringForRegion: (CLRegion *) region
{
if ([self insideRegion: region location: manager.location])
[self locationManager: manager
didEnterRegion: region];
}
From apple's documentation:
Monitoring of a geographical region begins immediately after
registration for authorized apps. However, don’t expect to receive an
event right away, because only boundary crossings generate an event.
In particular, if the user’s location is already inside the region
at registration time, the location manager doesn’t automatically
generate an event. Instead, your app must wait for the user to cross
the region boundary before an event is generated and sent to the
delegate. To check whether the user is already inside the boundary
of a region, use the requestStateForRegion: method of the
CLLocationManager class.
So I ended up doing this:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic, strong) NSDictionary *regionDictionary;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// setup regions in case you have multiple regions
self.regionDictionary = #{#"com.test" : #"2FAE2A83-1634-443B-8A0C-56704F81A181"};
// setup location manager
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
if([self.locationManager respondsToSelector:#selector(requestAlwaysAuthorization)]) {
[self.locationManager requestAlwaysAuthorization];
}
[self.locationManager startUpdatingLocation];
//start monitoring for all regions
for (NSString *key in self.regionDictionary.allKeys) {
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:self.regionDictionary[key]] identifier:key];
[self.locationManager startMonitoringForRegion:beaconRegion];
}
}
- (void)locationManager:(CLLocationManager*)manager didEnterRegion:(CLRegion *)region {
if (region.identifier.length != 0) {
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:self.regionDictionary[region.identifier]] identifier:region.identifier];
[self.locationManager startRangingBeaconsInRegion:beaconRegion];
}
}
- (void)locationManager:(CLLocationManager*)manager didExitRegion:(CLRegion *)region {
if (region.identifier.length != 0) {
CLBeaconRegion *beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[[NSUUID alloc] initWithUUIDString:self.regionDictionary[region.identifier]] identifier:region.identifier];
[self.locationManager stopRangingBeaconsInRegion:beaconRegion];
}
}
- (void)locationManager:(CLLocationManager*)manager didRangeBeacons:(NSArray*)beacons inRegion:(CLBeaconRegion*)region {
// Beacon found!
CLBeacon *foundBeacon = [beacons firstObject];
NSLog(#"UUID:%#; major:%#; minor:%#;", foundBeacon.proximityUUID.UUIDString, foundBeacon.major, foundBeacon.minor);
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region {
if ([region isKindOfClass:[CLBeaconRegion class]] && state == CLRegionStateInside) {
[self locationManager:manager didEnterRegion:region];
}
}
- (void)locationManager:(CLLocationManager *) manager didStartMonitoringForRegion:(CLRegion *) region {
[manager requestStateForRegion:region];
}
I don't get GPS location updates. I'm using an iPad2 to run the code. This is my code:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
//location
locationManager = [[[CLLocationManager alloc] init] autorelease]; // Create new instance of locMgr
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
NSLog(#"locationManager startUpdatingLocation"); //this core runs correctly
...
}
- (void)locationManager:(CLLocationManager *)manager
didFailWithError:(NSError *)error
{
NSLog(#"Error: %#", [error description]);
}
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
location = [newLocation description];
NSLog(#"new location: %#", location); //never printed
}
Should I allow location in the preferences ? I was expecting the iPad asking permissions for to share the location but I didn't get any.
thanks
Is locationManager a retain property? If not, make it one and add self. in front of the alloc+init line so the locationManager isn't released at the end of that method:
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
Also add [locationManager release]; in the dealloc.
I've actually removed autorelease and added release in dealloc, and that's it.