Swift, Detect Ibeacons on The Background and Send Notifications When in Range

iBeacon Notification when the app is not running

Yes, it's possible and should be automatic.

After you have created a CLBeaconRegion and started monitoring on it, Location Services will keep track of whether your phone is in or out of the region, even when your app isn't running. If you app isn't running during a transition, iOS will launch your app into the background for a few seconds to call the appropriate CLLocationManagerDelegate methods.

I found out the above behavior through experimentation with my own app, but have also witnessed it with Apple's AirLocate sample program. With AirLocate, if you set up a monitoring region then reboot your phone, AirLocate will still deliver a local notification as soon as the phone enters the region.

Take care when testing this, because sometimes it takes up to 4 minutes after turning on/off an iBeacon before iOS recognizes the region state transition. EDIT: As of the iPhone 5, apps will typically use hardware acceleration to detect beacons within a few seconds, and if hardware acceleration is not available, it can take up to 15 minutes.

EDIT 3: AS of iOS 13, you must make sure the user actually grants background permission and not "only once" or "when in use" permission which are heavily pushed by the operating system in the dialogs they present to the user. See here for details.

EDIT 2: As of iOS 8, you need to make sure you have called and successfully obtained locationManager.requestAlwaysAuthorization() as locationManager.requestWhenInUseAuthorization() only lets beacons be detected in the foreground.

I have posted a detailed discussion on how this all works in this blog post.

Beacon range in background

When an app is in the background and it gets a didRangeBeacons callback, it only gets 5 seconds to run by the operating system before it is suspended. This will close any web service connections that are open at that time. You can extend this background running time from 5 seconds to 180 seconds upon request. Below is an example in Swift 3 that shows how to do that.

var threadStarted = false
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid

func extendBackgroundRunningTime() {
if (self.backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return
}

print("Attempting to extend background running time")

self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
})

if threadStarted {
print("Background task thread already started.")
}
else {
threadStarted = true
DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async {
while (true) {
// A dummy tasks must be running otherwise iOS suspends immediately
Thread.sleep(forTimeInterval: 1);
}
}
}
}

By adding code like this, it is much more likely that your web service call will complete before iOS suspends your app.

You can call the extendBackgroundRunningTime() from your didRangeBeacons or didEnterRegion methods.

iBeacons and local notifications

In order to do this, you need to get the app to run in the background while beacons are in the vicinity. This would allow you to periodically check for updated content associated with the beacon and then display notifications under two conditions:

  1. When a beacon first appears, and there is a message associated with that beacon.
  2. The message associated with the beacon above changes during the time the beacon is still visible.

As you mention, the second item is a problem, because you need a way to continually check to see if there is updated content despite the fact that iOS will suspend your app within 5 seconds of beacon detection in the background.

A few options, each of which have downsides:

  1. You can use a custom hardware beacon that changes its identifier every minutes or so (e.g. the minor goes back and forth between 0 and 1). This would allow you to monitor two regions and re-trigger on each every minute the beacon is in the vicinity. Downside: This requires building a custom beacon.

  2. You can make your app request an extra 3 minutes of background running time during which you can check for changed messages. Downside: You only get three minutes to display changed messages.

  3. You can specify extra background modes in your .plist so you can continue running in the background. Downside: Apple won't approve your app for distribution in the store unless you have a good reason to run in the background (e.g. a navigation app or a music player app.)

  4. You can send a push notification to your app each time the message changes, which would wake up your app in the background so you could display an updated notification if a beacon is in the vicinity. Downside: Setting up push notifications are a bit complex, delivery can be slow, and is not guaranteed.

Read here for more info on some of these options: http://developer.radiusnetworks.com/2014/11/13/extending-background-ranging-on-ios.html

iBeacon monitoring and ranging in background

The problem you describe is common. Other than extending background ranging time (which you have already done), there is no magic bullet on iOS.

Two additional techniques may help. Both of these techniques involve getting a new entry event as you pass from one beacon to the next:

  1. Get hardware beacons that allow you to tune down the transmitter power (my company's RadBeacon products allow this) so the transmissions do not overlap. This way you get an exit event then a new enter event as you move from beacon to beacon.

  2. Redesign your identifier schene so the major field is dedicated to identifying up to 20 different regions (based on UUID and major 1-20). You then monitor for all of these regions. Your individual beacons can still use the minor however you want and specifically as the key to trigger messaging. When placing your beacons, you make sure that none with overlapping transmissions share the same major. This will ensure a new background entry event as you move from one to the other.

A simple code to detect any beacon in swift

SWIFT 3:

First of all you should add the CoreLocation.Framework

  • In the .Plist file add the key/string NSLocationAlwaysUsageDescriptionwith appropriate string

  • In your Object add the CLLocationManagerDelegate

  • Add the CLLocationManagerDelegate delegate methods in this example i will just add the didRangeBeacons method

func locationManager(_ manager: CLLocationManager, didRangeBeacons
beacons: [CLBeacon], in region: CLBeaconRegion) {
print(beacons) }
  • Create and initialise the locationManager

    var locationManager : CLLocationManager = CLLocationManager()

  • Create the CLBeaconRegion

    let beaconRegion : CLBeaconRegion = CLBeaconRegion(
    proximityUUID: NSUUID.init(uuidString:"****-****-****-****-******") as! UUID,
    identifier: "my beacon")
  • Add the delegate to your Object locationManager.delegate = self

  • Request the Location authorization from the user with

locationManager.requestAlwaysAuthorization()

Now let's start the Range

  locationManager.startRangingBeacons(in: beaconRegion)

This will automatically call the delegate method

   func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
print(beacons)

}

Note : if you want to monitor beacon's entry/ exit state you need to add

locationManager.startMonitoring(for: beaconRegion)

Finally : Make sure that your beacon is turned ON and you are testing the iBeacon Frame Enjoy :D



Related Topics



Leave a reply



Submit