CLLocationManager monitoredRegions (NSSet) 不正确,或者其他原因?

CLLocationManager monitoredRegions (NSSet) is not correct, or maybe something else?

我想在每次用户经过商店数组中的一家商店时检查,我有 20 多家商店,所以我编写了一个函数来找到离用户位置最近的 20 家商店并监控它们。该列表正在 locationManager: didUpdateLocations 上更新,我还将旧的 20 个受监控区域替换为新的 20 个最近的商店位置。

问题是当用户进入一个区域时,locationManager: didEnterRegion 没有被定期调用。

我还注意到,当我检查 locationManager.monitoredRegions NSSet 时,由于某种原因,那里的区域是错误的(我用 if 句子检查过,所以也许它们是正确的,并且只是更短?)。

如果有人能检查我的代码并可能注意到我做错了什么,那真的对我有帮助!

我的代码:

monitorLocationViewController.m(滚动查看完整代码):

-(void)getStoresArrays:(NSNotification*)notification
{
    //Fetching "allStoresArray"(NSArray of all the stores sent from another class using NSNotificationCenter) and adding it to "allStores"(NSMutableArray)
    NSDictionary *storesCategoriesArrays=[notification userInfo];
    self.allStores=[storesCategoriesArrays objectForKey:@"allStoresArray"];

    //Calling "locationChangeHandler" for monitoring
    [self locationChangeHandler];
}

#pragma mark - CLLocationDelegate methods
//Being called when user's location updated
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
    //If "allStores"(NSMutableArray) isn't nil - calling "locationChangeHandler" to update monitoring
    if (self.allStores!=nil) {
        [self locationChangeHandler];
    }
}
//Being called when user enters a monitored region
-(void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
    NSLog(@"Entered");
}

#pragma mark - Closest stores sorting methods
//Sorting closest stores to the user's location and adding the 20 closest store to "twentyClosestStores"(NSMutableArray)
-(void)sortClosestStores
{
    //Sorting "allStores"(NSMutableArray) from the closest "Store" to the furthest.
    [self.allStores sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        //Creating "location1"(CLLocation) and "location2"(CLLocation) and initializing each with "obj1"(id) and "obj2"(id) coordinates
        CLLocation *location1=[[CLLocation alloc] initWithLatitude:((Store*)obj1).geoPoint.latitude longitude:((Store*)obj1).geoPoint.longitude];
        CLLocation *location2=[[CLLocation alloc] initWithLatitude:((Store*)obj2).geoPoint.latitude longitude:((Store*)obj2).geoPoint.longitude];

        //Creating "dist1"(float) and setting its value to the distance between "location1"(CLLocation) and the user's location
        float dist1 =[location1 distanceFromLocation:self.locationManager.location];
        //Creating "dist2"(float) and setting its value to the distance between "location2"(CLLocation) and the user's location
        float dist2 = [location2 distanceFromLocation:self.locationManager.location];

        //If the distances are equal - the order will stay the same
        if (dist1 == dist2) {
            return NSOrderedSame;
        }
        else if (dist1 < dist2) { //If "dist1"(float) is smaller than "dist2"(float) - "dist2"(float) will be before "dist1" in the array
            return NSOrderedAscending;
        }
        else { //else - "dist2"(float) will be before "dist1" in the array
            return NSOrderedDescending;
        }
    }];

    //If "twentyClosestStores"(NSMutableArray) is nil
    if (self.twentyClosestStores==nil) {
        //Initializing "twentyClosestStores"(NSMutableArray)
        self.twentyClosestStores=[NSMutableArray array];
    }

    //If "previousTwentyStores"(NSMutableArray) is nil
    if (self.previousTwentyStores==nil) {
        //Initializing "previousTwentyStores"(NSMutableArray)
        self.previousTwentyStores=[NSMutableArray array];
    }
    //Setting "previousTwentyStores"(NSMutableArray) to "twentyClosestStores"(NSMutableArray)
    self.previousTwentyStores=self.twentyClosestStores;
    //Cleaning (reInitializing) "twentyClosestStores"(NSMutableArray)
    self.twentyClosestStores=[NSMutableArray array];

    //Adding indexes 0-19 of "allStores"(NSMutableArray) (20 closest stores to the user's current location) to "twentyClosestStores"(NSMutableArray)
    for (int i = 0; i < 20; i++) {
        [self.twentyClosestStores addObject:[self.allStores objectAtIndex:i]];
    }
}

#pragma mark - Start/stop monitoring methods
//For updating monitoring
-(void)locationChangeHandler
{
    //If "allStores"(NSMutableArray) isn't nil
    if (self.allStores!=nil) {
        //Finding the 20 closest stores to he user's location and adding it to "twentyClosestStores"(NSMutableArray)
        [self sortClosestStores];
        //Stop monitoring "previousTwentyStores"(NSMutableArray) (20 closest stores before user's location  updated)
        [self stopMonitoringStores];
        //Start monitoring "twentyClosestStores"(NSMutableArray)
        [self startMonitoringClosestStores];
    }
}
//Start monitoring "twentyClosestStores"(NSMutableArray)
-(void)startMonitoringClosestStores
{
    //If monitoring isn't availible for "CLCircularRegion"
    if (![CLLocationManager isMonitoringAvailableForClass:[CLCircularRegion class]]) {
        NSLog(@"Monitoring is not available for CLCircularRegion class");
    }

    //Run on all "twentyClosestStores"(NSMutableArray)'s objects
    for (Store *currentStore in self.twentyClosestStores) {
        //Creating "region"(CLCircularRegion) and setting it to "currentStore"(Store)'s circular region
        CLCircularRegion *region=[currentStore createCircularRegion];

        //Start monitoring "region"(CLCircularRegion)
        [self.locationManager startMonitoringForRegion:region];
    }
}
//Stop monitoring "previousTwentyStores"(NSMutableArray) (20 closest stores before user's location  updated)
-(void)stopMonitoringStores
{
    //Run on all "previousTwentyStores"(NSMutableArray)'s objects
    for (Store *currentStore in self.previousTwentyStores) {
        //Creating "region"(CLCircularRegion) and setting it to "currentStore"(Store)'s circular region
         CLCircularRegion *region=[currentStore createCircularRegion];

        //Stop monitoring "region"(CLCircularRegion)
        [self.locationManager stopMonitoringForRegion:region];
    }
}

//Finding a store for region
-(Store*)storeForRegion:(CLCircularRegion*)region
{
    //Creating "latitude"(CGFloat) and "longtitude"(CGFloat) and setting it to "region"(CLCircularRegion)'s center.latitude/longtitude
    CGFloat latitude=region.center.latitude;
    CGFloat longtitude=region.center.longitude;

    //Run on all "allStores"(NSMutableArray)'s objects
    for (Store *currentStore in self.allStores) {

        //If "currentStore"(Store)'s latitude and longtitude is equal to "latitude"(CGFloat) and longtitude(CGFloat)
        if (currentStore.geoPoint.latitude==latitude&&currentStore.geoPoint.longitude==longtitude) {
            //Returning "currentStore"(Store)
            return currentStore;
        }
    }
    //Store not found - returning nil
    NSLog(@"No store found for this region: %@",[region description]);
    return nil;
}

Store.m:

    //Creating and returning a "CLCircularRegion" object of the store
-(CLCircularRegion*)createCircularRegion
{
    //Creating "region"(CLCircularRegion) and initializing it with current store information (self) and radios of 200m
    CLCircularRegion *region=[[CLCircularRegion alloc] initWithCenter:self.geoPoint radius:200 identifier:self.identifier];

    //Setting "region"(CLCircularRegion)'s notifyOnEntry to YES
    region.notifyOnEntry=YES;

    //Returning "region"(CLCircularRegion)
    return region;
}

注意:正在调用委托方法,甚至 didEnterRegion: 但由于某些原因并非总是如此。

解决问题:

(我决定不使用区域监控,自己做)

 -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
{
    [self checkIfNearStore]; //Not being called in background
}

-(void)checkIfNearStore
    {
        for (Store *currentStore in self.allStores) {
            if ([currentStore.circularRegion containsCoordinate:self.locationManager.location.coordinate]&&currentStore.alreadySendNotification==NO) {
                NSLog(@"Entered: %@",[[self storeForRegion:currentStore.circularRegion] address]);
                currentStore.alreadySendNotification=YES;
                [self.storesAlreadySentNotifications addObject:currentStore];
            }
        }

        for (Store *currentStore in self.storesAlreadySentNotifications) {
            if (![currentStore.circularRegion containsCoordinate:self.locationManager.location.coordinate]) {
                currentStore.alreadySendNotification=NO;
            }
        }
    }

非常感谢!

区域半径

请查看下面关于 startMonitoringForRegion 方法的讨论,请注意 Apple 表示该区域的值最大为 400 在以后的设备上效果最好。他们还表明,平均可以在 3 到 5 分钟内收到回复。我不确定您是如何测试该区域的 entry/exit 的,但可能是您没有留出足够的时间通过。

区域监控

此外,我建议您设置断点以在代码的战略点检查 CLLocationManager 的 monitoredRegions 属性(当您希望区域更新时) .在使用您的自定义代码重新填充区域数组之前,您可能会考虑清空区域数组(请参阅下面的 即:header)。我会非常仔细地检查该区域,以确保发生预期的行为。

过滤器

有许多过滤器可以在位置发生重大变化时通知 CLLocationManager。您应该设置这些值并检查它们是否对更新频率有影响。另请注意我展示的其他属性和方法,包括 desiredAccuracy 和启动重要更新的方法。阅读对其中每一个的讨论,看看它们是否适用于您的用例。尝试在需要的地方应用更改,以强制设备在适合您的情况时进行更新。

命中测试用户位置

您可能希望使用用户位置实现特定 CLCircularRegion 的周期性 hit-testing。这应该允许您进一步优化代码中预定 times/locations 处的位置更新。例如,您可能希望使用精度和距离过滤器来优化用户更新,然后在委托方法中针对数组中最近的区域调用查询。最终效果是每当设备从最后已知位置显着移动时强制执行区域状态检查。

位置变化的后台更新

我建议您给出与 startMonitoringSignificantLocationChanges method (You must use this method to resolve the issue). Apple details the steps that you need to get the updates when the app is in the background. Basically you will have to intercept a key in your app delegate method. Fortunately this SO question 相关的讨论的关闭 re-read 有示例代码。但在跳转到代码之前,请按照建议查看讨论,因为它会让您更清楚地了解如何掌握此行为。

部分答案

喜欢很多问题。这需要一些部分答案来解决您需要的功能。首先确保您已检查上面提供的注释,然后尝试使用此链接中的某些链接来获取特定所需任务的部分答案。

How can I disable any CLRegion objects registered with -startMonitoringForRegion?

即:

Useful responses on how to refine geofencing

A simplified tutorial of geofencing

(您是否在位置管理器上使用了 requestAlwaysAuthorization()?)

诺塔贝内

我建议您阅读有关方法和属性的讨论,以了解 Apple 的提示。您还可以随意 cmd-click 代码编辑器中的方法和属性,并查看 header 文件中的注释。这些将提供文档或快速帮助中未提供的来自 Apple 的更多见解。

举个简单的例子,以下 header 评论似乎与您的 objective 相关。您可能希望实现和监视评论中提到的委托方法和 requestStateForRegion 方法 header:

/*
 *  locationManager:didDetermineState:forRegion:
 *
 *  Discussion:
 *    Invoked when there's a state transition for a monitored region or in response to a request for state via a
 *    a call to requestStateForRegion:.
 */
@available(iOS 7.0, *)
optional public func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion)