How to get a beacons advertisement data in iOS background mode - ios

I am trying to implement an app that simply detects a beacon and displays a notification while the app is in background mode.
I originally implemented CBCentralManager and received events in didDiscoverPeripheral which worked great but as soon as it goes into background mode it stops receiving events.
Since background mode is a requirement I implemented the CLLocationManager. I marked location, background-central and background-peripheral as background modes in info.plist. I also added NSLocationAlwaysUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUsageDescription to info.plist.
The relevant code is like the following:
locMgr = new CLLocationManager();
locMgr.PauseLocationUpdatesAutomatically= false;
locMgr.RequestAlwaysAuthorization();
locMgr.AllowsBackgroundLocationUpdates = true;
var nsuuid = new NSUuid(uuid.ToString());
var nsidentifier = new NSString(identifier);
CLBeaconRegion region = new CLBeaconRegion(nsuuid, nsidentifier);
region.NotifyOnExit = truel
region.NotifyOnEntry = true;
region.NotifyEntryStateOnDisplay = true;
locMgr.StartMonitoring(region);
public void OnRegionEntered(object sender, CLRegionEventARgs e)
{
is there anyway to get advertising info (instance id) of beacon in here?
anything to identify the beacon besides the proximity id which is the same for multiple beacons?
}
I have a backend server that holds additional info about the beacon that i'd like to call to get name/message. but this is keyed on the instance id of the beacon.
Does anyone know of a way to get the beacon info in background mode? can i connect to peripheral or set up cblcentralmanager in the OnRegionEntered or any other way?
Thanks for any help!

You can setup silent push notification in ios. In which you have to create one web service which will take latitude and longitude from app after certain time duration and will return list of beacons available surrounded with particular region for that latitude and longitude.
Than after, You have to implement this method in your app delegate class:
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
This method will be called when your app will get any push notification from server. You have to update your app according to the list that you get.

You can simply start beacon ranging at the same time you start beacon monitoring. So in addition to this:
locMgr.StartMonitoring(region)
Do this:
locMgr.StartRangingBeacons(in: region)
If you do this, then for about 10 seconds after the first time you detect a beacon region (even in the background) you will get a callback to the following delegate method at a rate of once per second:
didRange(beacons: beacons, region: region)
The above parameter beacons will have an array of all the beacons matching that region as CLBeacon objects, and each one will contain the full identifiers: proximityUUID, major, minor

Related

Starting locations updates after enter beacon region monitored Swift

I'm making a ibeacon region monitoring app with location updates when the user enter into this region (app not in foreground). This location updates must be configured as kCLLocationAccuracyBestForNavigation accuracy (because I have to make a tracking while the use remain in the region,
subscribe to me significant changes is not enough). Everything works well, but after 20 seconds (sometimes 1 minute o more) I stop receiving locations updates. I put all the keys in info.plist for always location usage, I include the background modes in capabilities section and locations updates on background.
I configure the locationManager with different configurations and always the SO stops my locations updates. I'm using IOS 12 and Iphone 7 for testing.
The way I configure CLLocationManager:
self.locationManager.desiredAccuracy
=kCLLocationAccuracyBestForNavigation
self.locationManager.activityType = .automotiveNavigation
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.delegate = self
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.pausesLocationUpdatesAutomatically = false
Start location updates (when user enter in Ibeacon Region):
func beaconManager(_ manager: KTKBeaconManager, didEnter region:
KTKBeaconRegion) {
self.locationManager.startUpdatingLocation()
}
And finally, in didUpdate locations i call a web service:
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
//Call web service using alamofire
}
I ask for your help to know if I am performing the settings correctly for the purpose I want to perform and any clue that lets me know why the operating system kills my process to get locations updates
Getting regular location updates in the background on iOS is tricky. The operating system is designed to keep apps from constantly running in the background to optimize battery usage, and it suspends them after a period of time unless you have several things exactly right.
You need to do three things:
You must get obtain always location permission from the user (as you say you've done).
You must add the following entry to your Info.plist. This will allow your app to run indefinitely in the background, however if you wish to submit your app to the App Store, this entry will also declare to reviewers that it is a background location app, and you will need to convince them that it provides a location-based benefit to the user, and that the user is aware of this benefit.
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
You must maintain a background thread to keep your app alive. It doesn't matter if you actually do anything in this background thread. Just having it be active keeps iOS from suspending your app.
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
func extendBackgroundRunningTime() {
if (backgroundTask != UIBackgroundTaskIdentifier.invalid) {
// already started
return
}
NSLog("Attempting to extend background running time")
self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
NSLog("Background task expired by iOS. Cannot run again until a new beacon region event")
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskIdentifier.invalid
})
DispatchQueue.global().async {
while (true) {
let backgroundTimeRemaining = UIApplication.shared.backgroundTimeRemaining
// This will be a very large number if you have proper permissions
// If not, it will generally count down from 10 seconds once you are in the
// background until iOS suspends your app.
NSLog("Thread background time remaining: \(backgroundTimeRemaining)")
Thread.sleep(forTimeInterval: 1.0)
}
}
}

Parsing Beacon Information in didEnterRegion Method of Altbeacon Library

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();

CLLocationManager Region Monitoring: Detect Arrival in Suspended State

I'm looking for a way that I can track that a user has arrived near a designated set of co-ordinates. The functionality needs to work while the application is in the background (preferably within 100 metres). Also, to preserve the battery, I ideally do not want to get too many co-ordinate readings (perhaps a reading every 10 minutes for no longer than a couple of hours).
There are a couple of ways that I have tried to accomplish this task, but have been unable to obtain the desired result:
Background Timer:
I had added a background task in (App.delegate)
func applicationDidEnterBackground(_ application: UIApplication)
Which executed a repeated Timer.scheduledTimer to get co-ordinates and process
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
to detect if the user was within range. This method worked if applied in the short-term, but only until the application was suspended, which was about 3 minutes. Ideally, I would not want to get co-ordinates this frequently.
Region Monitoring:
I had initialised the CLLocationManager as shown below:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.activityType = .otherNavigation
locationManager.requestAlwaysAuthorization()
}
The LocationManager starts when the application enters into the background:
func applicationDidEnterBackground(_ application: UIApplication) {
self.monitorRegionAtLocation(center: CLLocationCoordinate2D(latitude: x, longitude: y), identifier: id)
locationManager.startUpdatingLocation()
}
Code for monitoring of region:
func monitorRegionAtLocation(center: CLLocationCoordinate2D, identifier: String ) {
// Make sure the app is authorized.
if CLLocationManager.authorizationStatus() == .authorizedAlways {
// Make sure region monitoring is supported.
if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
// Register the region.
let maxDistance = 200.0
let region = CLCircularRegion(center: center,
radius: maxDistance, identifier: identifier)
region.notifyOnEntry = true
region.notifyOnExit = false
locationManager.startMonitoring(for: region)
}
}
}
And I added a didEnterRegion function block for CLLocationManager:
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if let region = region as? CLCircularRegion {
let identifier = region.identifier
print("FOUND: " + identifier)
}
}
The code appears to work for detecting entry into a region, however the co-ordinates are not updating while in the background.
Additional Information
I have the Background Modes of Location Updates and Background Fetch enabled
I have supplied values for 'Location Always Usage Description' and 'Location When in Use Usage Description' in the Info.plist
The App Settings shows 'Always' permission against the Location
I believe that there has to be a better way of operating these kinds of checks in the background, but I haven't discovered any method of detecting other movements in the background.
Any direction on this matter would be greatly appreciated, and if you need any more information, please let me know and I'll provide what I can.
UPDATE:
I have modified the approach following the advice of comments below to use Region Monitoring.
Any location update/monitoring requires it's location manager to be configured properly so that it can work to the best to provide the desired location update. It's important to check some point when doing background location update:
1. Check background modes of location updates and background fetch should be enable
2. Check 'Location Always Usage Description' and 'Location When in Use Usage Description' in the Info.plist should be provided
3. Check if you want to pause in between location update - if yes then you need to provide activity type so that location manager can determine best way to pause location update for you
4. Check if you want to apply distance filter - you want user(device) to move some minimum amount for location manager to send updated location
5. Check if you want desired accuracy- This may cause power drain for certain accuracy type
In your code I can see location manager is configured with some of the parameter but missing accuracy and distance filter for background mode.
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.activityType = .otherNavigation
Also, if you see pause location update property in Apple doc it says:
For apps that have in-use authorization, a pause to location updates
ends access to location changes until the app is launched again and
able to restart those updates. If you do not wish location updates to
stop entirely, consider disabling this property and changing location
accuracy to kCLLocationAccuracyThreeKilometers when your app moves to
the background. Doing so allows you to continue receiving location
updates in a power-friendly manner.
Essentially it tells that if you want disable pause then you have to keep accuracy level (kCLLocationAccuracyThreeKilometers). Which I guess is missing in your approach.
Also, you can check this link which actually starts background task and then starts location manager monitoring inside the background task.
Hope it helps.
The question says "CLLocationManager Region Monitoring: Detect Arrival in Background". And this is very much possible, but detecting anything after being killed is not possible (from iOS 7).
Whenever user swipe ups your app app-switcher, iOS takes it as the user doesn't wish the app to be running in the background, and so all the call-backs are stopped.
This answer, this answer and this answer also says the same thing. However Apple doc is a little confusing.
My personal observation is that app gets called even in Killed mode but very rarely.
And about getting the location, whenever the delegate method of geofencing is called, you can get location easily.
And the background modes are really not needed for your requirement.
And unfortunately (fortunately for iOS user as they save battery) we don't really have a way to get location just for 1 hr after app being killed.

Problems with connecting to IBeacon

I am using the example from here to connect IBeacon. I believe that my UUID is correct. But the Event RegionEntered is never called and e.Beacons.Length in DidRangeBeacons event is always 0.
locationMgr.DidRangeBeacons += (object sender, CLRegionBeaconsRangedEventArgs e) => {
var a = e.Region;
if (e.Beacons.Length > 0) {
//make notification
}
}
The difference from above mentioned sample is that I use the IBeacon instead of IPad.
Check to be sure you know the ProcimityUUID of your beacon by using the Locate app for iOS. You will need to configure the app with your ProximityUUID.
If the app will not detect your beacon, the beacon may be misconfigured or you may not have the proper UUID.
EDIT: I have added instructions for how to scan for your ProximityUUID here.

Location Notification's based on Latest iOS Feature

I would like to know whether the following is implementable ?
I want my iOS app to give users a notification when the reach a particular location as set in the app (even if the app is killed)
You can achieve this with the new User Notification framework introduced in iOS 10.
There have a UNLocationNotificationTrigger which let you specify a CLRegion. A notification will be post when the user’s device enters or leaves the CLRegion. Use the CLRegion object to specify whether to deliver notifications on entry, on exit, or both.
Refer to:
UserNotifications framework:
https://developer.apple.com/reference/usernotifications
UNLocationNotificationTrigger:
https://developer.apple.com/reference/usernotifications/unlocationnotificationtrigger
When the app is killed you can't , but you can still do it in background mode.
First Way:
UNLocationNotificationTrigger notifies user when user enters the specified location or exit the specified location this link provides exact implementation.
Second Way:
monitoring significant changes can do this for you but the result may not be 100%.
call following method from applicationDidEnterBackground and applicationWillTerminate:;
- (void)startSignificantChangeUpdates
{
// Create the location manager if this object does not
// already have one.
if (nil == locationManager)
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
[locationManager startMonitoringSignificantLocationChanges];
}
and when app enters foreground applicationWillEnterForeground: call following method (modify it as per your requirements i.e. accuracy & distance filter)
- (void)startStandardUpdates
{
// Create the location manager if this object does not
// already have one.
if (nil == locationManager)
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
// Set a movement threshold for new events.
locationManager.distanceFilter = 500; // meters
[locationManager startUpdatingLocation];
}
From apple documentation:
The significant-change location service delivers updates only when there has been a significant change in the device’s location, such as 500 meters or more. It’s crucial that you use the significant-change location service correctly, because it wakes the system and your app at least every 15 minutes, even if no location changes have occurred, and it runs continuously until you stop it.
For 100% accuracy you can involve server end and send silent notifications from server which will give your application a 30 second window to determine user location and fire a local notification.

Resources