Check if user is still within CLRegion after 30 seconds - 1 minute - ios

I'm currently sending local notifications to users when they enter regions. However, I don't want to notify them immediately because sometimes they will drive past these regions and don't want to be notified about them all of the time.
So whenever a user enters the region, didEnterRegion is called. I want to wait 30 seconds (or a configurable amount of time), then check if the user is still in the region (which I know how to do already) and if the user is still in that region, then notify the user.
Android has a way of doing this with a dwell property.
I know I can use dispatch_after(), but I can't gain the main thread back when the phone is asleep (or the app is suspended).
The phone will wake up automatically, by Apple's design, when a region is entered. When that happens, I want to wait for 30 seconds, then check again if user is still in location. Here's a code snippet that doesn't currently work:
// triggered when user enters monitored region.
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
// Wait 60 seconds and if user is still within region, try to send notification
delay(60){
self.locationManager.requestStateForRegion(region)
}
}
func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) {
// only notify user if user is still within this region.
if state == CLRegionState.Inside{
if region is CLCircularRegion {
handleRegionEvent(region)
}
}
}

There's a way to do this:
when the app gets notified that the user has entered a region, it should schedule a local notification for 30 seconds after. You need to set an identifier in this notification.
Now, if nothing happens this notification will be delivered to the user.
What could happen that prevents this?
The user leaving the region.
So, when the app gets notified that the user has exited a region, it should look for a scheduled notification with some known identifier and remove it as it's no longer relevant.

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)
}
}
}

Make a service call while application is in background state

I have to hit server for every 30 seconds while application is in background state.if I had written code in applicationDidEnterBackground method it is getting called only once but I need to hit server continuously for every 30 seconds while app is in background.
Please use HSLocationManager for your requirement. I have achieved the same requirements in one of my project
Location manager that allows getting background location updates every
n seconds with desired location accuracy.
Advantage:
OS will never kill our app if the location manager is currently
running.
Give periodically location update when it required(range is between 2 -
170 seconds (limited by max allowed background task time))
Customizable location accuracy and time period.
Low memory consumption(Singleton class)
Default time to retrieve location is 30 sec and accuracy is 200.
static let timeInternal = 30
static let accuracy = 200
Update:
Yes, You can do it by writing API call in didUpdateLocations method.
func scheduledLocationManager(_ manager: HSLocationManager, didUpdateLocations locations: [CLLocation]) {
logh("Make API Call here...")
}

Perform task (code execution/ network call) in background when user enters/exits a location region in iOS

System: In my application, I am using geofencing (monitoring a region). Whenever user enters or exits the monitored area or region, the app shows a local notification if the app is in the background or even terminated. This is working perfectly fine. The app is able to show local notificaiton.
Now I also need to submit this information (if the user is inside or outside of the monitored area) via HTTP POST call to app's backend server.
Problem: App makes API call in LocationManager's delegate methods but sometimes it works and sometimes it does not. It seems that code execution stops randomly if the app is not in foreground state.
Code sample
// MARK: - Location Manager Delegate
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if region is CLCircularRegion {
showLocalNotification(forRegion: region)// Works
updateUserEntryAPICall(region: region) // Sometimes works
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
if region is CLCircularRegion {
showLocalNotification(forRegion: region) // Works
updateUserExitAPICall(region: region) // sometime works
}
}
I did not turn on background mode capability in iOS. Still, the app is able to show local notifications. Do I need to turn it on in order to make network call to work?
Please help.
This looks like you're using a regular URLSession to make your API request. You need to make sure that you're doing this on a session that handles running in the background, e.g. by initializing it like so:
let session = URLSession(configuration: .background(withIdentifier: "foo"))

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

Ranging beacons interval

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.

Resources