iOS 11 - Location update not received after adding

2019-06-09 08:12发布

问题:

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

回答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:

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