Display "Cannot find iBeacon" message - ios

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!

Related

How to call "didUpdateLocation" method from viewDidLoad?

I am making a weather app which gets temperature info by sending current coordinates. So I am using CLLocationManager. I haven't make any HTTP request yet. I would like to see "Location Unavailable" on my screen but it is not working.
I am following a tutorial and wrote exactly same code but in tutorial it is working. In debug session, it doesn't jump to didFailwithError or didUpdateLocations methods.I added all necessary things in my
info.plist and my phone's location services are open. According to my research locationManager.startUpdatingLocation() should call automatically
these methods but when I run my app nothing appears on UI.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("you got the location")
}
//Write the didFailWithError method here:
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
cityLabel.text = "Location Unavaiable"
}
And
let locationManager = CLLocationManager ()
override func viewDidLoad() {
super.viewDidLoad()
//TODO:Set up the location manager here.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
You are updating location too soon, in fact before you get the permission from user.
After you ask for permission
locationManager.requestWhenInUseAuthorization()
Delegate method will call
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
}
Now its time to start updates. Guarding the status is a good idea since you don't want to start updating unless user permits it.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status == .authorizedWhenInUse else { return }
manager.startUpdatingLocation()
}
Now you'll get location updates
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
}

Get location updates in background - only for Country change iOS

What is the best way in core location to get background location updates only when there is change in country?
You can use the reverseGeocodeLocation of the CLGeocoder to get the current country for your location.
func locationManager(_ manager: CLLocationManager, didUpdateLocations objects: [CLLocation]) {
let location = objects.last!
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location!) { (places, error) in
if(error == nil){
if(places != nil){
let place: CLPlacemark = places![0]
let country = place.country
// do something if its changed
}
} else {
//handle error
}
But the issue will be you need to be monitoring location for this to happen. You can use startMonitoringSignificantLocationChanges as one option or you could set desired accuracy to something big like kCLLocationAccuracyThreeKilometers both of which will reduce the amount of power used by location updates.
What you are looking for is called geofencing, there are great articles about it. Any way you'll need to implement functions like
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) (CLLocationManagerDelegate)
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) (CLLocationManagerDelegate)
open func requestAlwaysAuthorization() (CLLocationManager)
func startMonitoring(for region: CLRegion) (CLLocationManager)

CLLocationManager not monitoring regions

I am perplexed...I had this working today and now I don't know what is going on. It isn't my iBeacon because I can locate it with the Locate iOS app. I am creating my own location manager, not sure if you would call it a subclass exactly because I am subclassing NSObject. Here it is:
//
// LocationManager.swift
// onebeacon
//
// Created by Eamon White on 2/24/18.
// Copyright © 2018 EamonWhite. All rights reserved.
//
import Foundation
import CoreLocation
protocol LocationManagerDelegate: class {
func locationManagerDidUpdateLocation(_ locationManager: LocationManager, location: CLLocation)
func locationManagerDidUpdateHeading(_ locationManager: LocationManager, heading: CLHeading, accuracy: CLLocationDirection)
func locationManagerDidEnterRegion(_ locationManager: LocationManager, didEnterRegion region: CLRegion)
func locationManagerDidExitRegion(_ locationManager: LocationManager, didExitRegion region: CLRegion)
func locationManagerDidDetermineState(_ locationManager: LocationManager, didDetermineState state: CLRegionState, region: CLRegion)
func locationManagerDidRangeBeacons(_ locationManager: LocationManager, beacons: [CLBeacon], region: CLBeaconRegion)
}
class LocationManager: NSObject, CLLocationManagerDelegate {
private var locationManager: CLLocationManager!
weak var delegate: LocationManagerDelegate?
var beaconsToRange: [CLBeaconRegion]
var currentLocation: CLLocation!
override init() {
self.beaconsToRange = []
super.init()
self.locationManager = CLLocationManager()
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.headingFilter = kCLHeadingFilterNone
self.locationManager.pausesLocationUpdatesAutomatically = false
self.locationManager.delegate = self
self.enableLocationServices()
}
func enableLocationServices() {
self.checkStatus(status: CLLocationManager.authorizationStatus())
}
func checkStatus(status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
// Request when-in-use authorization initially
locationManager.requestAlwaysAuthorization()
break
case .restricted, .denied:
// Disable location features
print("send an alert that the app will not function")
break
case .authorizedWhenInUse:
locationManager.requestAlwaysAuthorization()
// Enable basic location features
break
case .authorizedAlways:
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()
self.monitorBeacons()
// Enable any of your app's location features
break
}
}
func monitorBeacons() {
print("monitorBeacons()")
if CLLocationManager.isMonitoringAvailable(for:
CLBeaconRegion.self) {
print("monitorBeacons().monitoringIsAvailable")
// Match all beacons with the specified UUID
let proximityUUID = UUID(uuidString:
"12345678-B644-4520-8F0C-720EAF059935")
let beaconRegion = CLBeaconRegion(
proximityUUID: proximityUUID!,
major: 0x0001,
minor: 0x0002,
identifier: "iBeacon")
beaconRegion.notifyEntryStateOnDisplay = true;
self.locationManager?.startMonitoring(for: beaconRegion)
print("\(String(describing: self.locationManager?.monitoredRegions)) + monitoredRegions")
}
}
//MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations {
self.delegate?.locationManagerDidUpdateLocation(self, location: location)
}
self.currentLocation = manager.location
}
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
self.delegate?.locationManagerDidUpdateHeading(self, heading: newHeading, accuracy: newHeading.headingAccuracy)
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if region is CLBeaconRegion {
// Start ranging only if the feature is available.
if CLLocationManager.isRangingAvailable() {
locationManager?.startRangingBeacons(in: region as! CLBeaconRegion)
// Store the beacon so that ranging can be stopped on demand.
beaconsToRange.append(region as! CLBeaconRegion)
}
}
self.delegate?.locationManagerDidEnterRegion(self, didEnterRegion: region)
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
self.delegate?.locationManagerDidExitRegion(self, didExitRegion: region)
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if region is CLBeaconRegion {
print("determined state of beacon")
// Start ranging only if the feature is available.
if CLLocationManager.isRangingAvailable() {
print("determined state of beacon and started ranging")
locationManager?.startRangingBeacons(in: region as! CLBeaconRegion)
// Store the beacon so that ranging can be stopped on demand.
beaconsToRange.append(region as! CLBeaconRegion)
}
}
self.delegate?.locationManagerDidDetermineState(self, didDetermineState: state, region: region)
}
func locationManager(_ manager: CLLocationManager,
didRangeBeacons beacons: [CLBeacon],
in region: CLBeaconRegion) {
self.delegate?.locationManagerDidRangeBeacons(self, beacons: beacons, region: region)
}
func locationManagerShouldDisplayHeadingCalibration(_ manager: CLLocationManager) -> Bool {
return true
}
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
self.checkStatus(status: status)
}
}
In my ViewController I use it in a very straight forward fashion like this (at the top: delegate methods...somewhat inconsequential for the problem, at the bottom: instantiation):
import UIKit
import SceneKit
import ARKit
import CoreLocation
class ViewController: UIViewController, ARSCNViewDelegate, LocationManagerDelegate {
func locationManagerDidUpdateLocation(_ locationManager: LocationManager, location: CLLocation) {
}
func locationManagerDidUpdateHeading(_ locationManager: LocationManager, heading: CLHeading, accuracy: CLLocationDirection) {
}
func locationManagerDidEnterRegion(_ locationManager: LocationManager, didEnterRegion region: CLRegion) {
}
func locationManagerDidExitRegion(_ locationManager: LocationManager, didExitRegion region: CLRegion) {
}
func locationManagerDidDetermineState(_ locationManager: LocationManager, didDetermineState state: CLRegionState, region: CLRegion) {
}
func locationManagerDidRangeBeacons(_ locationManager: LocationManager, beacons: [CLBeacon], region: CLBeaconRegion) {
print("\(beacons) + beacons for ranging")
if beacons.count > 0 {
let nearestBeacon = beacons.first!
let major = CLBeaconMajorValue(truncating: nearestBeacon.major)
let minor = CLBeaconMinorValue(truncating: nearestBeacon.minor)
print("major: \(major)")
print("minor: \(minor)")
print("accuracy: \(nearestBeacon.accuracy)")
switch nearestBeacon.proximity {
case .immediate:
print("--- immediate ---")
case .near:
print("--- near ---")
case .far:
print("--- far ---")
case .unknown:
print("--- proximity unknown ---")
}
}
}
var sceneView: ARSCNView!
var locationManager: LocationManager!
override func viewDidLoad() {
super.viewDidLoad()
sceneView = ARSCNView()
locationManager = LocationManager()
locationManager.delegate = self
...
My console output is:
2018-02-24 20:40:30.927542-0500 onebeacon[1275:423523] [DYMTLInitPlatform] platform initialization successful
2018-02-24 20:40:32.799470-0500 onebeacon[1275:423484] Metal GPU Frame Capture Enabled
2018-02-24 20:40:32.801237-0500 onebeacon[1275:423484] Metal API Validation Enabled
monitorBeacons()
monitorBeacons().monitoringIsAvailable
Optional(Set([CLBeaconRegion (identifier:'iBeacon', uuid:12345678-B644-4520-8F0C-720EAF059935, major:1, minor:2)])) + monitoredRegions
All of the iBeacon details are correct, there is no way they could have changed from earlier today as it has been plugged in this whole time and not accessed. The problem summarized: it appears to add the region to the "regions-to-be-monitored" list, but the monitoring never seems to start because I get no console messages relaying the iBeacon information.
UPDATE
I will leave the answer I provided as it might be part of it (maybe not)...but now it seems to be starting twice:
2018-02-24 21:35:13.341162-0500 onebeacon[1345:444027] [DYMTLInitPlatform] platform initialization successful
2018-02-24 21:35:16.504017-0500 onebeacon[1345:443977] Metal GPU Frame Capture Enabled
2018-02-24 21:35:16.505384-0500 onebeacon[1345:443977] Metal API Validation Enabled
monitorBeacons()
monitorBeacons().monitoringIsAvailable
Optional(Set([CLBeaconRegion (identifier:'iBeacon', uuid:12345678-B644-4520-8F0C-720EAF059935, major:1, minor:2)])) + monitoredRegions
2018-02-24 21:35:16.747004-0500 onebeacon[1345:443977] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2018-02-24 21:35:16.747969-0500 onebeacon[1345:443977] [MC] Reading from public effective user settings.
monitorBeacons()
monitorBeacons().monitoringIsAvailable
Optional(Set([CLBeaconRegion (identifier:'iBeacon', uuid:12345678-B644-4520-8F0C-720EAF059935, major:1, minor:2)])) + monitoredRegions
As you can see the monitorBeacons()... lines happen twice, I checked to make sure that there is no other instantiation of LocationManager and there isn't.
UPDATE
I narrowed it down...the problem seems to be that the didDetermineState function is not firing initially, I have to walk out of range (or close to it) from the beacon...and then walk back and the didDetermineState method fires, does anyone know why it isn't firing when I am starting in the radius of the beacon?
UPDATE
It appears that my phone is recognizing that it is in a region because when I walk out of the region, the didExitRegion method fires, and then the didDetermineState method...odd because the state should already be determined if the didExitRegion method is firing:
2018-02-24 23:01:09.650445-0500 onebeacon[422:33762] [App] if we're in the real pre-commit handler we can't actually add any new fences due to CA restriction
2018-02-24 23:01:09.651978-0500 onebeacon[422:33762] [App] if we're in the real pre-commit handler we can't actually add any new fences due to CA restriction
2018-02-24 23:01:15.007844-0500 onebeacon[422:33762] [App] if we're in the real pre-commit handler we can't actually add any new fences due to CA restriction
2018-02-24 23:01:15.007893-0500 onebeacon[422:33762] [App] if we're in the real pre-commit handler we can't actually add any new fences due to CA restriction
2018-02-24 23:02:00.002451-0500 onebeacon[422:33762] Status bar could not find cached time string image. Rendering in-process.
...(put in for easier reading)...
did exit region --- CLBeaconRegion (identifier:'iBeacon', uuid:12345678-B644-4520-8F0C-720EAF059935, major:1, minor:2)
determined state of beacon
determined state of beacon and started ranging
[] + beacons for ranging
[] + beacons for ranging
[] + beacons for ranging
[] + beacons for ranging
[] + beacons for ranging
[] + beacons for ranging
[] + beacons for ranging
What is the pre-commit handler warning? Are those relevant?
My didDetermineState function stopped working on initial load of the app. If I walk outside the range of the beacon, and back into range - the didDetermineState method fires (along with enter/exit region methods). I purchased another iBeacon...and it works fine...also, curiously, when I use both beacons together, both beacons get detected on initial load by didDetermineState. I am not sure why didDetermineState stopped working for the one beacon...when being used in isolation.

didEnterRegion & didDetermineState not being called when the app is not running while implementing Geofencing without iBeacon

I am trying to implement Geofencing with out iBeacon. The
didStartMonitoringFor is getting called, but
didEnterRegion & didDetermineState not being called when the app is not running.
I am calling the requestState in didStartMonitoringFor. So the didDetermineState is being called for the first time. But not getting called while location changes. Can some one help me ?
Thanks in advance !
Thank God ! I got the answer by myself.
I have been doing all declaration and delegate method implementation in Home screen classs. I changed all part to AppDelegate class. Also made some changes to properties for location manager as
locationManager.delegate = self
locationManager.activityType = .automotiveNavigation
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.distanceFilter = 10.0
locationManager.requestAlwaysAuthorization()
Also implemented both delegate methods
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion){
manager.requestState(for: region)
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if state == .inside
{
addNotification(region: region)
}
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
addNotification(region: region)
}
And it worked !

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

Resources