On iOS, in my application delegate I start region monitoring and as soon as I enter in a beacon region I start the ranging logic, using locationManager:didRangeBeacons:inRegion. According to the Apple documentation, this method should be called only when the the region comes within the range or out of the range or when the range changes.
My problem is that I get a call to this method every second as long as I am inside the region. How to decrease the number of calls to this method while still ranging?
locationManager:didRangeBeacons:inRegion is called once per second, no matter what. Each time it's called, the beacons parameter will contain an array of all beacons that the app can currently see, ordered by proximity. There's no way to limit the frequency at which this method is called, short of stopping ranging.
When monitoring regions (instead of ranging), your app will have didEnterRegion: and didExitRegion called, along with didDetermineState:. See this answer for a little more detail.
According to the Docs:
"The location manager calls this method whenever a beacon comes within range or goes out of range. The location manager also calls this method when the range of the beacon changes; for example, when the beacon gets closer."
Whats probably happening is the range is changing slightly which is causing the behaviour you describe.
Why is this a problem
EDIT:
IN the background you will get notified of entering regions via the app delegate method:
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region{}
You can use this to determine the state:
if(state == CLRegionStateInside)
{
//Inside a region:
}
else if(state == CLRegionStateOutside)
{
//Outside a region
}
else {
//Something else
}
You can use this to gather a limited amount of information or prompt the user to load the application via a local notification. When your app resumes you can then gather more information via the locationManager.
Related
I would like to use the didEnterRegion method in association with the RegionBootstrap or MonitorNotifier in my application. Currently I'm using the RegionBootstrap but perhaps the MonitorNotifier is better for my application.
In particular I'm adding an iBeacon parser to the beaconmanager and then setting "Id1" of a region to look for the UUID portion of my iBeacon and setting "Id2" and "Id3" to Null. Though they are set to Null in the Region, I would like to be able to parse the information from those locations upon entering the didEnterRegion method. I'm using "Id2" (Major) and "Id3" (Minor) to provide random identification parameters of the beacons.
This information along with a portion of the data from the UUID would then be sent in a notification to the phone user. When testing, I'm entering the didEnterRegion method but the data that is provided is only that which matches the set region of "Id1". If someone could provide any insight at all, it would be greatly appreciated!
I would also like to receive the didEnterRegion method for the same iBeacon every 10 seconds, but with testing it appeared that once that particular iBeacon was seen once, didEnterRegion wouldn't get a subsequent call again. Any way to clear that the iBeacon was captured so that subsequent captures could happen?
I'm trying to keep the battery usage as low as possible and when using the scanRecord data from a onNonBeaconLEScan to parse the information, I'm noticing significant battery drain even when setting the foreground and background time "BetweenScanPeriod" to something really large. I really only need to see that the iBeacon entered the region and pull the information, then 10 seconds later do it again.
Intended application flow -
User enters region of beacon with matching UUID (ID1)
Beacon information from ID2 and ID3 are parsed and sent along with ID1 to user via notification
10 seconds later user receives another notification with same data
repeat until person leaves region or iBeacon stops transmitting
The simplest way to get the information you need is to enable ranging in the didDetermineStateForRegion callback:
public void didDetermineStateForRegion(int state, Region region) {
beaconManager.startRangingBeaconsInRegion(region);
beaconManager.addRangeNotifier(this);
}
public void didRangeBeaconsInRegion(Region region, List<Beacon> beacons) {
for (Beacon beacon : beacons) {
Identifier id2 = beacon.getId2();
Identifier id3 = beacon.getId3();
// Now do something with id2 and id3
}
}
The didRangeBeaconsInRegion callback will be made every 1100 ms with default settings, but you can change this to be 10 seconds if you wish with a line like this the first time you access the BeaconManager:
beaconManager.setScanPeriod(10000l);
beaconManager.setBetweenScanPeriod(0l);
In terms of battery, if you want to be getting scan updates every 10 seconds, you will be using a lot of battery, because this means doing almost constant bluetooth scans. In the background, you may wish to back off and do a 10 second scan only once every 5 minutes with this:
beaconManager.setBackgroundScanPeriod(10000l);
beaconManager.setBackgroundBetweenScanPeriod(290000l);
BackgroundPowerSaver powerSaver = new BackgroundPowerSaver();
Currently I am using CLLocationmanager to monitor for BLE beacon regions in iOS.
I know I can range beacons if i want more than 20 regions but unfortunately ranging would not allow me to register entry(RegionDidEnter) and exit(RegionDidExit) events as far as I know.
In my use case I need to trigger actions on user's entry and user's exit in a particular beacon region even when app is in killed state or in background.
I need a efficient way to do this as if I look for significant location changes it also uses battery and also using beacons would not make much sense then if i use GPS.
When didEnter happens, iOS will launch your app into the background and give it a few seconds of execution time to handle the event. You can use that time to start ranging, receive the ranging results, and since ranging always provides full UUID/major/minor info, trigger an appropriate action based on that.
Pseudo-code:
let myUUID = x
startMonitoring(myUUID)
func onDidEnter {
startRanging(myUUID)
}
func onDidRange(beacons) {
if beacons.empty { return } // keep ranging until we find something
let major = beacons.first.major
if major == 1 { show("Welcome to X") }
if major == 2 { show("Welcome to Y") }
stopRanging(myUUID)
}
To ensure that your app doesn't get put back to sleep before it manages to range a beacon, you can also use a background task, then the (pseudo-)code would look something like:
func onDidEnter {
self.task = beginBackgroundTask(expirationHadler: {
// our background time is up, iOS requires us to finish our work
stopRanging(myUUID)
endBackgroundTask(self.task)
})
startRanging(myUUID)
}
func onDidRange(beacons) {
if beacons.empty { return }
let major = beacons.first.major
if major == 1 { show("Welcome to X") }
if major == 2 { show("Welcome to Y") }
stopRanging(myUUID)
endBackgroundTask(self.task)
}
You can add a workaround to this. Register only those regions near to the user location. When the location changes, you can remove regions that are now farther way and add regions coming up on the user’s path.
To save battery when dealing with location, register for significant-change location updates or make use of defer location updates or use visit monitoring.
Why Core Location limited to 20
Regions are a shared system resource, and the total number of regions
available systemwide is limited. For this reason, Core Location limits
to 20 the number of regions that may be simultaneously monitored by a
single app. To work around this limit,
I've set up Google Nearby API for my objective-c project to scan for beacons.
The app detects the beacons fine when moving into the range of a beacon but it does't work if I start the app when I'm already in range. I have to walk away from the beacon and return.
I am not using background scanning. The lib version I use is: 0.10.0
My code is:
[GNSMessageManager setDebugLoggingEnabled:YES];
_messageManager = [[GNSMessageManager alloc] initWithAPIKey:#"..."];
_beaconSubscription = [_messageManager subscriptionWithMessageFoundHandler:^(GNSMessage *message) {
NSLog(#"beacon found: %#",message);
...
} messageLostHandler:^(GNSMessage *message) {
NSLog(#"beacon lost: %#",message);
...
} paramsBlock:^(GNSSubscriptionParams *params) {
params.deviceTypesToDiscover = kGNSDeviceBLEBeacon;
params.beaconStrategy = [GNSBeaconStrategy strategyWithParamsBlock:^(GNSBeaconStrategyParams *params) {
params.includeIBeacons = YES;
}];
}];
I know about the Core Location Framework didEnterRegion / didExitRegion methods that are called only when crossing the boundaries of a beacon region and that I can use didDetermineState method but how does the NearbyAPI work on the inside with these and how can I make the app detect the beacons already in range at startup using it?
This is indeed a bug in the way Nearby Messages monitors iBeacon regions. It uses didEnterRegion/didExitRegion, and as you stated, if you're already in a region when scanning starts, didEnterRegion isn't called.
I've experimented with using didDetermineState, and with a bit of work I'm now able to handle this case. We will include this in the next bug fix release.
In the meantime, here's a trick you can use to avoid the problem while testing your app: Put your beacon into a metal enclosure (a faraday cage), and remove it from the enclosure after your app starts scanning for beacons. This simulates movement into the beacon region. I use a small cocktail shaker for my faraday cage, but a small amount of aluminum foil also works.
I am using both geofencing and beacons in my swift project. But their entered and exit method clash everytime I enter or leave any of the region. To be more specific, how can I execute different method to corresponding beacon or geofence region ??e.g.
I want to show a notification when the user enters the geofence region and display other page when it enters the beacon region.
Geofence regions and beacon regions are both regions, so the same didEnterRegion method is called to notify you about both. You need to write your didEnterRegion method to check the class of the region with an if statement. (I forget the syntax for that in Swift.)
EDIT:
The Objective-C code would be
if ([region isMemeberOfClass: [CLBeaconRegion class]])
{
//beacon code
}
else
{
//geofence code
}
You could certainly convert that to Swift syntax, but David Young showed the better way in his comment, below:
if let beaconRegion = region as? CLBeaconRegion
{
//beacon code
}
else
{
//geofence code
}
(Thanks David. Answering technical questions from my iPad, before I've had coffee, is only of limited use.)
An app I'm working on currently sets up region monitoring with the user's current location when the app is backgrounded. When the app becomes active again I am trying to stop monitoring for the region, but it seems to work intermittently with the majority of the time resulting in it failing to act as expected. When the app is backgrounded, I start monitoring for the region and it works fine when I log the details:
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
DDLogInfo(#"CREATED REGION: %#", region.identifier);
}
Which results in the following log:
"CREATED REGION: regionFor: [real lat here, real lon here] with radius: 100"
When the app wakes up, I call the following function:
- (void)stopMonitoringAllRegions {
DDLogInfo(#"About to stop monitoring for %d regions", [locationManager monitoredRegions].count);
// stop monitoring for any and all current regions
for (CLRegion *region in [[locationManager monitoredRegions] allObjects]) {
[locationManager stopMonitoringForRegion:region];
}
DDLogInfo(#"After stopping, we're currently monitoring for %d regions", [locationManager monitoredRegions].count);
}
Which results in the following log about 75% of the time:
"About to stop monitoring for 1 regions"
"After stopping, we're currently monitoring for 1 regions"
and infrequently I get what seems like a success:
"About to stop monitoring for 1 regions"
"After stopping, we're currently monitoring for 0 regions"
I've tried a couple of things with no success. The regions I'm creating are CLCircularRegions, which inherit from CLRegion so that should work regardless, but in the for-loop I've changed CLRegion to CLCircularRegion with no effect. I was originally using [locationManager monitoredRegions] by itself, which returns an NSSet, so I thought using the allObjects function to get the array would fix the issue, but it hasn't.
I also thought it might be an issue with mutating the array while enumerating, but the only other post I saw on SO said that the above worked for them...
Am I missing something?
If you read up on monitoredRegions, it represents all monitored regions of all CLLocationManager instances, and so is probably controlled by a private dispatch queue - which would explain the delays.
My suggestion would be to keep your own mutable array (or set) around, using it to keep track of what regions are monitored and which are not, and not rely on the location manager for that collection.
Now that its clear you cannot rely on immediate changes to it, I'd design around it rather than try to find some heuristic that seems (today) to work but bites you later.