0

I have been having trouble with location services in iOS 11 for both "Allow while Use" and "Always Allow". Works without issue in iOS < 11. Followed this thread in trying to fix but still doesn't work. What am I missing? Thank you in advance.

  1. I have UITabViewController in my app and a UINavigationController inside each tab.
  2. I have a singleton LocationManager class. I'm setting my UINavigationController's RootViewController as delegate the ViewController's viewWillAppear to receive location updates and removing in viewWillDisappear.
  3. Now, When I launch the app, before the tab bar is created, I can see the location update is being called in the LocationManager Class.
  4. But when I add my UIViewController as delegate and give startUpdatingLocation I'm not receiving the location update in my UIVIewController.
  5. Then I press Home button and exit the app. Again immediately launch the app and I get the location update in my delegate method.

I have added all three location authorization description in my Info.plist file.

Info.Plist:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Blah Blah Blah</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Blah Blah Blah</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Blah Blah Blah</string>

LocationController.m:

#import "LocationController.h"

//static int LOCATION_ACCESS_DENIED = 1;
//static int LOCATION_NETWORK_ISSUE = 2;
//static int LOCATION_UNKNOWN_ISSUE = 3;
enum {
    LOCATION_ACCESS_DENIED = 1,
    LOCATION_NETWORK_ISSUE = 2,
    LOCATION_UNKNOWN_ISSUE = 3
};

static LocationController* sharedCLDelegate = nil;

@implementation LocationController
int distanceThreshold = 10.0; // in meters
@synthesize locationManager, currentLocation, locationObservers;

- (id)init
{
    self = [super init];
    if (self != nil) {
        self.locationManager = [[CLLocationManager alloc] init];
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        self.locationManager.distanceFilter = distanceThreshold;
        self.locationManager.pausesLocationUpdatesAutomatically = NO;
        [self.locationManager startMonitoringSignificantLocationChanges];
        self.locationManager.delegate = (id)self;
        if ([self.locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
            [self.locationManager requestWhenInUseAuthorization]; //I tried both commenting this line and uncommenting this line. Didn't make any difference
        }
        if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)])
        {
            [self.locationManager requestAlwaysAuthorization];
        }
        [self.locationManager startUpdatingLocation];
        [self.locationManager startUpdatingHeading];
        locationObservers = [[NSMutableArray alloc] init];

    }
    return self;
}

#pragma mark -
#pragma mark CLLocationManagerDelegate Methods

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    NSLog(@"locationManager  didUpdateLocations = %@",locations);
    CLLocation *newLocation = [locations lastObject];
    if (newLocation.horizontalAccuracy < 0) {
        return;
    }
    currentLocation = newLocation;

    for(id<LocationControllerDelegate> observer in self.locationObservers) {
        if (observer) {
//            CLLocation *newLocation = [locations lastObject];
//            if (newLocation.horizontalAccuracy < 0) {
//                return;
//            }
//            currentLocation = newLocation;

            NSTimeInterval interval = [currentLocation.timestamp timeIntervalSinceNow];
            //check against absolute value of the interval
            if (fabs(interval)<30) {
                [observer locationUpdate:currentLocation];
            }

        }
    }
}

- (void)locationManager:(CLLocationManager*)manager
       didFailWithError:(NSError*)error
{
    NSLog(@"locationManager didFailWithError: %@", error);

    for(id<LocationControllerDelegate> observer in self.locationObservers) {
        if (observer) {
            [observer failedToGetLocation:error];
        }
    }
    switch (error.code) {
        case kCLErrorDenied:
        {
            break;
        }
        case kCLErrorNetwork:
        {
            break;
        }
        default:

            break;
    }


}

#pragma mark - Singleton implementation in ARC
+ (LocationController *)sharedLocationInstance
{
    static LocationController *sharedLocationControllerInstance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        sharedLocationControllerInstance = [[self alloc] init];
    });
    return sharedLocationControllerInstance;
}

-(void) locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    NSLog(@"didChangeAuthorizationStatus = %i",status);
    if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) {
        [self.locationManager stopUpdatingLocation];
        [self.locationManager startUpdatingLocation];
    }
}
- (void) addLocationManagerDelegate:(id<LocationControllerDelegate>)delegate {
    if (![self.locationObservers containsObject:delegate]) {
        [self.locationObservers addObject:delegate];
    }
    [self.locationManager startUpdatingLocation];
}

- (void) removeLocationManagerDelegate:(id<LocationControllerDelegate>)delegate {
    if ([self.locationObservers containsObject:delegate]) {
        [self.locationObservers removeObject:delegate];
    }
}

+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedCLDelegate == nil) {
            sharedCLDelegate = [super allocWithZone:zone];
            return sharedCLDelegate;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

#pragma mark UIAlertViewDelegate Methods

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    switch (alertView.tag) {
        case LOCATION_ACCESS_DENIED:
        {
            if (buttonIndex == 1) {

                //[[UIApplication sharedApplication] openURL: [NSURL URLWithString: UIApplicationOpenSettingsURLString]];
                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=LOCATION_SERVICES"]];

            }


        }
        break;
        case LOCATION_NETWORK_ISSUE:
            break;
        case LOCATION_UNKNOWN_ISSUE:
            break;
        default:
            break;
    }
    [alertView dismissWithClickedButtonIndex:buttonIndex animated:YES];
}
@end

LocationController.h

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>

// protocol for sending location updates to another view controller
@protocol LocationControllerDelegate
@required
- (void)locationUpdate:(CLLocation*)location;
- (void)failedToGetLocation:(NSError*)error;
@end

@interface LocationController : NSObject<CLLocationManagerDelegate,UIAlertViewDelegate>


@property (nonatomic, strong) CLLocationManager* locationManager;
@property (nonatomic, strong) CLLocation* currentLocation;
@property (strong, nonatomic) NSMutableArray *locationObservers;


+ (LocationController*)sharedLocationInstance; // Singleton method

- (void) addLocationManagerDelegate:(id<LocationControllerDelegate>) delegate;
- (void) removeLocationManagerDelegate:(id<LocationControllerDelegate>) delegate;


@end

ViewController.m

- (void) viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"VC viewWillAppear");
    [locationControllerInstance addLocationManagerDelegate:self];
}

AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [LocationController sharedLocationInstance];
}
LoveMeSomeFood
  • 3,137
  • 7
  • 30
  • 53
  • I didn't look into your question, but it's always good to see the [latest core-location WWDC video](https://www.google.com/search?q=wwdc+2017+core+location&oq=wwdc+2017+core+loca&aqs=chrome.0.0j69i57.3918j0j7&sourceid=chrome&ie=UTF-8). Have you seen it? – mfaani Oct 07 '17 at 04:15
  • @Honey No, I haven't. Watching now. Thanks for the link – LoveMeSomeFood Oct 07 '17 at 04:18
  • @Honey I just finished watching it. I don't think there is anything else that I have missed. Also, background location service is not needed and hence not enabled. I suppose, I'm right in not enabling it. My issue is lil different than "not receiving the update". I receive the update. But I only after the app enters background and re-opened. – LoveMeSomeFood Oct 07 '17 at 04:57
  • You are asking for both significant location changes and updates. Maybe try disabling significant location to se if that changes anything? – jcaron Oct 07 '17 at 08:22
  • 1
    I would start by simplifying your code. If you aren’t enabling background location mode then there is no point in asking for “always” location permission; just ask for “when in use”. Is your device moving when you test? I can see a potential issue when you add the view controller as a delegate, the location may already have been delivered to the location delegate and the view controller will not receive an update until the location changes. – Paulw11 Oct 07 '17 at 12:17
  • @jcaron No. I'm not asking for significant location change. I'm asking only for updates. – LoveMeSomeFood Oct 08 '17 at 02:32
  • @Paulw11 Thanks for helping and pointing that out. Yes, I have removed the "AlwaysAuthorization" after seeing the latest core location wwdc video as suggested by Honey. No. My device is not moving. But I'm manually calling [startUpdatingLocation] as soon as I add a new delegate. But if I'm not getting the location update because the phone is not moving, would I get if I close the app and open again? – LoveMeSomeFood Oct 08 '17 at 02:37
  • @Paulw11, You are right. When I change the location while using the app,( in simulator), I do receive the location updates. Looks this this is new in iOS11. – LoveMeSomeFood Oct 08 '17 at 03:25
  • @Paulw11 Seems to be working well in iOS < 11. Have made some workaround and also adding Heider's code below. Thank you very much!! – LoveMeSomeFood Oct 08 '17 at 03:54
  • 1
    @Uma even though that’s probably not related to your issue, you are indeed asking for significant location changes in your `init` method. – jcaron Oct 08 '17 at 08:02
  • @jcaron Oh yea. That didnt catch my eyes. I have been overlooking that line all these time. Thanks for pointing that!! – LoveMeSomeFood Oct 09 '17 at 14:36
  • Still not getting continuous updates (via `didUpdateLocations`) under 11.2 Simulator.. Only on location changes. – bauerMusic Dec 20 '17 at 08:24

1 Answers1

1

I had one of the new devices reported this problem, as you know the the Location Manager usually calls this:

-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation

The bizarre thing is that the UserLocation object contains two coordinate objects:

1) userLocation.location.coordinate: This used to work fine, but for some reason it's returning NULL on IOS11 on some devices (it's unknown yet why or how this is behaving since IOS11).

2) userLocation.coordinate: This is another (same) object as you can see from the properties, it has the location data and continues to work fine with IOS11, this does not seem to be broken (yet).

So, with the example above, "I guess" that your:

  • (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations

Might be having the same problem (i.e. the array might be returning a NULL somewhere in the location object, but not the coordinate object, the solution I did on my code which gets one location at a time, is now fixed by by replacing userLocation.location.coordinate with userLocation.coordinate, and the problem gone away.

I also paste my function below to assist you further, hopefully it would help you to resolve yours too, notice that I have two conditions for testing one for sourcing the location object and the other for sourcing the coordinate object, one works fine now, and the other seems to be broken in IOS11:

-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
    Log (4, @"MapView->DidUpdateUL - IN");

    if (_OrderStartTrackingMode == enuUserMapTrackingMode_User)
    {
        if (userLocation)
        {
            if (userLocation.location)
            {
                if ( (userLocation.location.coordinate.latitude) && (userLocation.location.coordinate.longitude))
                {
                    [_mapLocations setCenterCoordinate:userLocation.location.coordinate animated:YES];
                } else {
                    if ( (userLocation.coordinate.latitude) && (userLocation.coordinate.longitude))
                    {
                        [self ShowRoutePointsOnMap:userLocation.coordinate];
                    }
                }
            }
        }

    } else if (_OrderStartTrackingMode == enuUserMapTrackingMode_Route) {

        if (userLocation)
        {
            if ( (userLocation.coordinate.latitude) && (userLocation.coordinate.longitude))
            {
                [self ShowRoutePointsOnMap:userLocation.coordinate];
            }
        }


    }

    Log (4, @"MapView->DidUpdateUL - OUT");
}

Needless to say, have you checked your settings for the Map object, you should have at least "User Location" enabled:

enter image description here

P.S. The Log function on the code above is a wrapper to the NSLog function, as I use mine to write to files as well.

Good luck Uma, let me know how it goes.

Regards, Heider

Heider Sati
  • 2,476
  • 26
  • 28
  • Heider, Thank you very much for the detailed answer. I just solved the issue following the comment made by @Paulw11. As you have mentioned that, in few devices, the location update was received but null and I've decided to add this to my code to address that too. Great find!! – LoveMeSomeFood Oct 08 '17 at 03:37