Use GPS with iBeacon in background - ios

is it possible to use GPS with iBeacon when app is in background?
I need to start update gps location when iPhone detect iBeacon only through 180s, I do something like this but it doesn't work :/
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
if !isInForeground {
extendBackgroundTime()
}
print(wasBeaconDetected)
for beacon in beacons {
if beacon.rssi < -10 {
wasBeaconDetected = true
nearbyBeacons.append(beacon)
}
}
print(beacons)
currentLocation = manager.location!.coordinate
if firstRegionDetected == "" {
firstRegionDetected = region.identifier
} else if firstRegionDetected == region.identifier {
analyzeScan(nearbyBeacons, currentLocation)
}
}
of course I start monitoring when determine State
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if isInBackground {
extendBackgroundTime()
}
}

Related

Trying to detect beacons but failed because isMonitoringAvailable always false

Hello i trying to detect and ranging beacons following by apple's docs article
but in my case CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) always gave me false thus i couldn't start monitoring
Of course i set Privacy for location and Location updates on background mode
this is my codes
func initializeLocationManager(){
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func rangeBeacons(){
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
let region = CLBeaconRegion(proximityUUID: UUID(uuidString: beacons[0].uuid)!, identifier: beacons[0].identifier)
locationManager.startMonitoring(for: region)
}else {
print("CLLocation Monitoring is unavailable")
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways {
rangeBeacons()
}
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if region is CLBeaconRegion {
// start monitoring
if CLLocationManager.isRangingAvailable() {
locationManager.startRangingBeacons(in: region as! CLBeaconRegion)
}
}
print("didEnter at \(region.identifier)")
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("didExit at \(region.identifier)")
}
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
if beacons.count > 0 {
let nearestBeacon = beacons.first!
switch nearestBeacon.proximity {
case .far:
print("far")
break
case .near:
print("near")
break
case .immediate:
print("It's behind yout")
break
case .unknown:
print("unknown")
}
}
}
but if i use locationManager.startRangingBeacons directly instead locationManager.startMonitoring(for: region), it works
but still problem didEnterRegion and didExitRegion are not called
what is the problem in my case
i want to follow exactly same with apple's docs article
It's unclear from the code shown what CLBeaconRegion.self means in context. Try instead using the defined region to see if monitoring is available.
func rangeBeacons(){
let region = CLBeaconRegion(proximityUUID: UUID(uuidString: beacons[0].uuid)!, identifier: beacons[0].identifier)
if CLLocationManager.isMonitoringAvailable(for: region) {
locationManager.startMonitoring(for: region)
}else {
print("CLLocation Monitoring is unavailable")
}
}
In practice, there is no real reason to call isMonitoringAvailable. Just start monitoring without this check. If for some reason it fails, you will get a callback to: locationManager:monitoringDidFailForRegion:withError
I believe the only reason why isMonitoringAvailable can be false for iBeacon (CLBeaconRegion) is when the device you're running the app on doesn't support Bluetooth 4.0.
This includes:
the simulator,
iPhone: 1st-gen, 3G and 3GS,
iPad: 1st-gen and 2nd-gen,
iPod Touch: 1st through 4th gen.
If you're running your app on any device newer than these, then the only remaining thing I can think of is, there's a problem with Bluetooth on your device. In which case, a reboot might help.

iOS 11: didExitRegion events are not being called reliably when monitoring beacons in the background

I am trying to implement background region monitoring for iBeacons using iOS's CoreLocation API. I seem to get inconsistent results when exiting an iBeacon region which I have called "Entry/Exit Door". In the background, the app sometimes gets the didExitRegion event on time, sometimes it gets the didExitRegion event many minutes later, and other times it doesn't get the didExitRegion event at all. I'm not sure if there is something obvious that I am doing wrong? I attached my Swift class that I am implementing for iBeacon region monitoring below:
import CoreLocation
import UIKit
import CoreBluetooth
class BeaconRegionMonitor: NSObject, CLLocationManagerDelegate {
var locationManager:CLLocationManager
var uuidDictionary:Dictionary<String, String> = ["Desks":"B0702880-A295-A8AB-F734-031A98A512DE", "Entry/Exit Door":"B0702880-A295-A8AB-F734-031A98A512DF"]
required override init() {
locationManager = CLLocationManager()
super.init()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways {
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
stopMonitoringOldRegions()
monitorUUIDS ()
getInitialStatesForRegions()
}
}
}
func stopMonitoringOldRegions ()
{
let monitoredRegions = locationManager.monitoredRegions
let uuidDictionaryIndenfiers = uuidDictionary.keys
let oldMonitoredRegions = monitoredRegions.filter {!(uuidDictionaryIndenfiers.contains($0.identifier))}
for oldMonitoredRegion in oldMonitoredRegions
{
NSLog("Stopped Monitoring \(oldMonitoredRegion.identifier)")
locationManager.stopMonitoring(for : oldMonitoredRegion)
}
}
func isRegionAlreadyBeingMonitored(regionToMonitor: CLRegion) -> Bool {
let monitoredRegions = locationManager.monitoredRegions
for region in monitoredRegions
{
if region.identifier == regionToMonitor.identifier
{
NSLog("Already Monitoring \(region.identifier)")
return true
}
}
return false
}
func monitorUUIDS() {
for identifierUuidPair in uuidDictionary
{
if let uuid = UUID(uuidString: identifierUuidPair.value) {
let beaconRegion = CLBeaconRegion(
proximityUUID: uuid,
identifier: identifierUuidPair.key)
if !isRegionAlreadyBeingMonitored(regionToMonitor: beaconRegion)
{
locationManager.startMonitoring(for : beaconRegion)
NSLog("Started Monitoring \(beaconRegion.identifier)")
}
}
}
}
func getInitialStatesForRegions ()
{
let monitoredRegions = locationManager.monitoredRegions
for region in monitoredRegions
{
locationManager.requestState(for : region)
}
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion){
showLocalNotification("DER: Entered \(region.identifier)", alertAction: "")
NSLog("DER: Entered \(region.identifier)")
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion){
showLocalNotification("DXR: Exited \(region.identifier)", alertAction: "")
NSLog("DXR: Exited \(region.identifier)")
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion){
if region is CLBeaconRegion {
if state == .inside {
showLocalNotification("DDSR: Inside \(region.identifier)", alertAction: "")
NSLog("DDSR: Inside \(region.identifier)")
}
else if state == .outside {
showLocalNotification("DDSR: Outside \(region.identifier)", alertAction: "")
NSLog("DDSR: Outside \(region.identifier)")
}
else {
showLocalNotification("DDSR: Unknown \(region.identifier)", alertAction: "")
NSLog("DDSR: State unknown for region \(region.identifier)")
}
}
}
func showLocalNotification(_ alertBody:String, alertAction:String) {
scheduleLocalNotification(alertBody, alertAction: alertAction, fireDate: Date())
}
func scheduleLocalNotification(_ alertBody:String, alertAction:String, fireDate:Date){
let alertBodyModified = alertBody.replacingOccurrences(of: "%", with: "%%")
let settings = UIApplication.shared.currentUserNotificationSettings
if settings?.types == .none {
print("can't schedule notification")
return
}
let notification = UILocalNotification()
notification.fireDate = fireDate
notification.alertBody = alertBodyModified
notification.alertAction = alertAction
notification.soundName = UILocalNotificationDefaultSoundName
UIApplication.shared.scheduleLocalNotification(notification)
}
}

Display "Cannot find iBeacon" message

My question is very simple. I would like to display an error message i.e. "Cannot find iBeacon" if iBeacon monitoring fails, after calling startSearchingForSessions via a button press after being called in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager = CLLocationManager()
if self.locationManager.responds(to: #selector(CLLocationManager.requestWhenInUseAuthorization)) {
self.locationManager.requestWhenInUseAuthorization()
}
self.locationManager.delegate = self
self.locationManager.pausesLocationUpdatesAutomatically = false
let uuid = UUID(uuidString: "869A6E2E-AE14-4CF5-8313-8D6976058A7A")
self.beaconRegion = CLBeaconRegion(proximityUUID: uuid!, identifier: "com.dejordan.myapp"
startSearchingForSessions()
}
func startSearchingForSessions() {
// Start looking for the beacons with that UUID and Identifier.
self.locationManager.startMonitoring(for: self.beaconRegion)
self.locationManager.startRangingBeacons(in: self.beaconRegion)
self.locationManager.startUpdatingLocation()
}
And handling the found beacons thusly:
// Required by the Location Manager.
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
self.locationManager.startRangingBeacons(in: self.beaconRegion)
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if state == CLRegionState.outside {
print("Cannot Find Beacon")
}
}
// Required by the Location Manager.
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
self.locationManager.stopRangingBeacons(in: self.beaconRegion)
}
// This is called if any beacons are found.
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
var result = Array<CLBeacon>()
for beacon in beacons {
result.append(beacon)
}
foundBeacons = result
// If we found any, we need to see
// what class they belong to based on information
// from Parse.
self.identifyFoundBeacons()
// We can stop looking for beacons now.
self.locationManager.stopMonitoring(for: self.beaconRegion)
self.locationManager.stopRangingBeacons(in: self.beaconRegion)
self.locationManager.stopUpdatingLocation()
}
I have implemented the delegate error methods in an attempt to find where this occurs but thus far in navigating the mounds of documentation on iBeacon I have come up fruitless.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location manager failed: \(error.localizedDescription)")
}
func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
print("Failed monitoring region: \(error.localizedDescription)")
}
Thank you!
If you simply want to know when beacons are not detected (vs. when there was a low-level failure to look for beacons), then simply use the following delegate method:
public func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if state == CLRegionState.outside {
print("Cannot find beacon")
}
}
Interestingly enough, didRangeBeacons in the delegate method
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion)
gets called with an empty array of [CLBeacon]s, wherein I can use the beacons array size to determine whether or not any beacons were found.
Not what I expected, but this has solved my problem!

How to detect a new iBeacon?

I range beacons and display them in my TableView. I need to detect when my app detects a new beacon. I try to do it in this way, but something goes wrong
var oldBeacons: [CLBeacon] = []
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
for beacon in beacons {
for oldBeacon in oldBeacons {
if beacon.minor != oldBeacon.minor, beacon.major != oldBeacon.major {
print("New Beacon")
} else {
print("Old Beacon")
}
}
}
oldBeacons = beacons
}
Iterating through two arrays won't easily work because if you ever see two beacons at the same time, you'll incorrectly think they are "new" because one is not the same as the other.
I typically use a Set to do this:
var detectedBeacons: Set<String>
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
for beacon in beacons {
let key = "\(beacon.proximityUUID) \(beacon.major) \(beacon.minor)"
if detectedBeacons.contains(key) {
print("Old Beacon")
}
else {
print("New Beacon")
detectedBeacons.insert(key)
}
}
}

Not able to detect Kontakt.io beacons in my iOS app

I had did beacon searching code as per Kontakt SDK Sample code as below. But I am getting beacon count always 0, while I am having 11 beacons near by my iPhone. Can any body help me over this?
I have initialise KTKBeaconManager in viewDidLoad method and then create region object and stopped any previous ranging service and then started new monitoring and ranging services.
And all time it calls didRangeBeacons with beacons count = 0. Not sure what exactly the issue. Its same code from their example code.
import UIKit
import KontaktSDK
class ViewController: UIViewController {
var beaconManager: KTKBeaconManager!
#IBOutlet var statusLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Initiate Beacon Manager
beaconManager = KTKBeaconManager(delegate: self)
beaconManager.requestLocationAlwaysAuthorization()
// Region
let proximityUUID = NSUUID(uuidString: "f7826da6-4fa2-4e98-8024-bc5b71e0893e")
let region = KTKBeaconRegion(proximityUUID: proximityUUID! as UUID, identifier: "com.weenggs.KontaktDemo")
// Region Properties
region.notifyEntryStateOnDisplay = true
beaconManager.stopMonitoringForAllRegions()
// Start Ranging
beaconManager.startMonitoring(for: region)
beaconManager.startRangingBeacons(in: region)
beaconManager.requestState(for: region)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController: KTKBeaconManagerDelegate {
func beaconManager(_ manager: KTKBeaconManager, didDetermineState state: CLRegionState, for region: KTKBeaconRegion) {
print("Did determine state \"\(state.rawValue)\" for region: \(region)")
statusLabel.text = "Did determine state \"\(state.rawValue)\" for region: \(region)"
}
func beaconManager(_ manager: KTKBeaconManager, didChangeLocationAuthorizationStatus status: CLAuthorizationStatus) {
print("Did change location authorization status to: \(status.rawValue)")
statusLabel.text = "Did change location authorization status to: \(status.rawValue)"
if status == .authorizedAlways{
// Region
let proximityUUID = NSUUID(uuidString: "f7826da6-4fa2-4e98-8024-bc5b71e0893e")
let region = KTKBeaconRegion(proximityUUID: proximityUUID! as UUID, identifier: "com.weenggs.KontaktDemo")
// Region Properties
region.notifyEntryStateOnDisplay = true
beaconManager.startMonitoring(for: region)
beaconManager.startRangingBeacons(in: region)
beaconManager.requestState(for: region)
}
}
func beaconManager(_ manager: KTKBeaconManager, monitoringDidFailFor region: KTKBeaconRegion?, withError error: Error?) {
print("Monitoring did fail for region: \(region)")
print("Error: \(error)")
statusLabel.text = "Monitoring did fail for region: \(region)"
}
func beaconManager(_ manager: KTKBeaconManager, didStartMonitoringFor region: KTKBeaconRegion) {
print("Did start monitoring for region: \(region)")
statusLabel.text = "Did start monitoring for region: \(region)"
}
func beaconManager(_ manager: KTKBeaconManager, didEnter region: KTKBeaconRegion) {
print("Did enter region: \(region)")
statusLabel.text = "Did enter region: \(region)"
}
func beaconManager(_ manager: KTKBeaconManager, didExitRegion region: KTKBeaconRegion) {
print("Did exit region \(region)")
statusLabel.text = "Did exit region \(region)"
}
func beaconManager(_ manager: KTKBeaconManager, didRangeBeacons beacons: [CLBeacon], in region: KTKBeaconRegion) {
print("Did ranged \"\(beacons.count)\" beacons inside region: \(region)")
statusLabel.text = "Did ranged \"\(beacons.count)\" beacons inside region: \(region)"
if let closestBeacon = beacons.sorted(by: { $0.0.accuracy < $0.1.accuracy }).first , closestBeacon.accuracy > 0 {
print("Closest Beacon is M: \(closestBeacon.major), m: \(closestBeacon.minor) ~ \(closestBeacon.accuracy) meters away.")
statusLabel.text = "\(statusLabel.text) Closest Beacon is M: \(closestBeacon.major), m: \(closestBeacon.minor) ~ \(closestBeacon.accuracy) meters away."
}
}
}
I reckon the common issue is that you forgot to set
Kontakt.setAPIKey("yourSuperSecretAPIKey")
and either one of those two permissions
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
If it's not the case then your beacons' batteries could be drained out.
Finally I was able to detect beacons using CBPeripheral class and identify uniquely based on received UUID with instance id( last 12 characters are instance id which are unique)

Resources