Unable to scan for iBeacons on macOS - macos

I am attempting a very simple AppKit app on MacOS which displays nearby iBeacons.
I don't appear to ever have region scanning available on my system though. Am I doing something wrong?
FYI: My system is a MacBook Pro (16-inch, 2019) running Catalina (10.15.7)
The target has 'Bluetooth' and 'App Location' enabled in the 'App Sandbox' capabilities section and 'Location' enabled under 'Hardened Runtime'.
The code below always gives the following:
LM Auth Status is now: Authorised Always
Beacon monitoring is not available
starting scanning
region monitoring failed: Error Domain=kCLErrorDomain Code=5 "(null)"
// ViewController.swift
// Beacons
import Cocoa
import CoreLocation
class ViewController: NSViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.requestAlwaysAuthorization()
locationManager.delegate = self
}
#IBAction func buttonTapped(_ sender: NSButton) {
self.startScanning()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func startScanning() {
print("starting scanning")
//UUID Obscured for forum post...
guard let uuid = UUID(uuidString: "xxxxxxxx-E4A1-4720-9034-xxxxxxxxxxxx") else {
print("Couldn't make UUID")
return
}
let beaconID = "com.monkeyfood.myBeaconRegion"
let beaconRegion = CLBeaconRegion(uuid: uuid, identifier: beaconID)
// let beaconRegion = CLBeaconRegion(uuid: UUID(), identifier: beaconID)
self.locationManager.startMonitoring(for: beaconRegion)
}
//. CLLocationManagerDelegate Methods
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
var newStatus = ""
switch status {
case .notDetermined:
newStatus = "Not determined"
case .restricted:
newStatus = "Restricted"
case .denied:
newStatus = "Denied"
case .authorizedAlways:
newStatus = "Authorised Always"
case .authorized:
newStatus = "Authorised"
default:
newStatus = "What?"
}
print("LM Auth Status is now: \(newStatus)")
// Check for iBeacon monitoring status
let regionMonitoringIsAvailable = CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self)
let not = regionMonitoringIsAvailable ? "" : "not "
print("Beacon monitoring is \(not)available")
}
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
if beacons.count > 0 {
// updateDistance(beacons[0].proximity)
print("\(beacons.count) beacons found.")
print("\(beacons[0].proximity) dist.")
} else {
// updateDistance(.unknown)
}
}
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("region entered... \(region)")
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("region exited... \(region)")
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
print("determined region state")
}
func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
print("region monitoring failed: \(error)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("LM failed \(error)")
}
}

I do not believe that Beacon monitoring with CoreLocation works on any MacOS version.
I looked at the class docs to confirm this and was surprised to see that the CLBeacon class docs do say it is supported on MacOS 10.15+. I don't know if this is a mistake, or simply an indication that the SDKs for MacOS 10.15+ support these classes even though you can't really use them.
The last time I worked with these APIs on MacOS was in 2018 when MacOS 2.14 was new. Back then, I had to use CoreBluetooth to do raw BLE advertisement scanning on MacOS and parse out beacons myself. I doubt anything has changed since then. I do not remember hearing anything about beacon support being added to MacOS, and just doing a quick search now, I can find no discussion of this starting with WWDC 2019. I doubt I missed such a change, but I would be thrilled to learn that I did!

Related

CLLocationManager: didChangeAuthorizationStatus not called in iOS 13

I have an app that has been working fine for several years, but location services are not working properly for iOS 13 on newer iPhones. Works fine on 7 and below. When the CLLocationManager is initialized the CLLocationManager: didChangeAuthorizationStatus method is not called.
Here is the code:
var firstMapLoad = true
let locationManager = CLLocationManager()
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
....
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print ("locationManager: didChangeAuthorization called")
if (CLLocationManager.locationServicesEnabled()) {
switch status {
case .notDetermined:
// Request when-in-use authorization initially
locationManager.requestWhenInUseAuthorization()
print ("locationManager: notDetermined & requestWhenInUseAuthorization")
break
case .authorizedWhenInUse:
// Enable basic location features
locationManager.startUpdatingLocation()
print ("locationManager: authorizedWhenInUse & startUpdatingLocation")
break
case .authorizedAlways:
// Enable any of your app's location features
//enableMyAlwaysFeatures()
break
case .restricted, .denied:
//determine if system or app loc svcs disabled
print ("locationManager: restricted or denied")
noAppLocationService()
break
#unknown default:
print("locationManager: unknown authorization state")
}
}else{
viewOnlyMode()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let GPSAccuracy:CLLocation = locations[0] as CLLocation
print ("locationManager: didUpdateLocations")
if let location = locations.last?.coordinate {
if (currentLocation.latitude != location.latitude) || (currentLocation.longitude != location.longitude) {
self.currentLocation = location
GPSHorizontalAccuracy = GPSAccuracy.horizontalAccuracy
// print("LocManAccuracy: \(GPSAccuracy.horizontalAccuracy)")
// print("LocManLatitude: \(GPSAccuracy.coordinate.latitude)")
// print("LocManLongitude: \(GPSAccuracy.coordinate.longitude)")
// print("LocManCurrLat: \(currentLocation.latitude)")
// print("LocManCurrLong: \(currentLocation.longitude)")
if firstMapLoad {
// Set the map’s center coordinate and zoom level.
let camera = GMSCameraPosition.camera(withTarget: self.currentLocation, zoom: 13)
self.viewMap?.animate(to: camera)
firstMapLoad = false;
}
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location Error = \(error.localizedDescription)")
}
When I run it on the iPhone 11 Pro Max simulator in Xcode, if the iPhone simulator has been reset and run the app for the first time I get the authorization alert with the 3 choices. I select "when in use" and the app works fine - the didChangeAuthorization function is called twice (once when location manager is initialized and then when I select "when in use"). didUpdateLocations is then called and the app gets location updates.
If I stop the app, delete it from the simulator and then re-run from Xcode the app does not request user permission for location services. didChangeAuthorization is not called when the location manager is initialized and thus location services are not started and the app doesn't receive any location updates. I have observed the same behavior on a real iPhone 11 Pro.
I tried setting the permission for the app to "never" in the app location settings, stopped the app and then deleted the app on the simulator. When I ran from Xcode the next time the app did not ask for location permissions, but did display an alert that location services were disabled. It appears that the settings from the previous installation were retained even though the app was deleted.
In my case, the delegate's method signature was wrong for iOS 14+
iOS <=13, deprecated in iOS 14
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
iOS 14+
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)
If you're targeting iOS <14, you should implement both

How do I make Location permission request trigger only when the user clicks on the map tab in a UITabBar?

I am building an app using UITabBar and one of the tab items is a map. I only want the user to have to respond to the LocationService() request when they tap on the map tab. However, when the app loads to the tabBar, the location permission request pops up first thing.
Here is the code for the LocationService()
import CoreLocation
protocol LocationServiceDelegate: class {
func authorizationDenied()
func setMapRegion(center: CLLocation)
}
class LocationService: NSObject {
var locationManager = CLLocationManager()
weak var delegate: LocationServiceDelegate?
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
private func checkAuthorizationStatus() {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
locationManager.requestAlwaysAuthorization()
case .denied:
delegate?.authorizationDenied()
case .authorizedAlways, .authorizedWhenInUse:
startUpdatingLocation()
default:
break
}
}
private func startUpdatingLocation() {
locationManager.startUpdatingLocation()
}
}
extension LocationService: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkAuthorizationStatus()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.stopUpdatingLocation()
if let location = locations.last {
delegate?.setMapRegion(center: location)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
The method was originally called in viewDidLoad so the map would center on the user's location, so I moved it to viewDidAppear, but I still get the same notice when the app launches.
How do I delay the permission request until the user actually taps on the map tab - in this case the DIY Stores tab?

Setting map along with user current location for osx

Greeting pros,
As I know we can make a map by using google map api for IOS like the code below here.
import UIKit
import GoogleMaps
/* For cocoa:
Import Cocoa
Import MapKit
*/
class ViewController: NSViewController, CLLocationManagerDelegate {
#IBOutlet var mapView: MKMapView!
var locationManager = CLLocationManager()
var didFindMyLocation = false
var strForCurLatitude = "";
var strForCurLongitude = "";
var currentLocation = locManager.location!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
print("User allowed us to access location")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error while get location \(error)")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation? = locationManager.location
let coordinate: CLLocationCoordinate2D? = location?.coordinate
print(coordinate!)
print(coordinate!.latitude)
print(coordinate!.longitude)
strForCurLatitude = "\(coordinate!.latitude)"
strForCurLongitude = "\(coordinate!.longitude)"
let camera = GMSCameraPosition.camera(withLatitude: coordinate!.latitude, longitude: coordinate!.longitude, zoom: 15)
let mapView = GMSMapView.map(withFrame: .zero, camera: camera)
mapView.isMyLocationEnabled = true
self.view = mapView
let marker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(coordinate!.latitude, coordinate!.longitude)
marker.map = mapView
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
But when I tried the similar method for osx (The difference is that I am using mapkit instead of google map), it says requestWhenInUseAuthorization()' is unavailable. I red this thread How to get user location ? [macOS] but it seems goes without clear resolved whether it is available to get current location for osx or not. So is it inaccessible to get current location for macOS/cocoa app? If it is not, then how to get current location in cocoa app?
I am pretty sure that many xcode programmers like me tried to solve this problem. Any answer you will get big appreciation tho. :)
requestWhenInUseAuthorization is unavailable on macOS.
Here's the source for a NSViewController subclass that checks for location manager and the current location successfully in Xcode 10.1 on macOS 10.13.6:
import Cocoa
import MapKit
import CoreLocation
class MapViewController: NSViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
print("location manager auth status changed to:" )
switch status {
case .restricted:
print("status restricted")
case .denied:
print("status denied")
case .authorized:
print("status authorized")
let location = locationManager.location
print("location: \(String(describing: location))")
case .authorizedAlways:
print("status authorized always")
case .notDetermined:
print("status not yet determined")
}
}
func locationManager(_ manager: CLLocationManager,
didFailWithError error: Error) {
print( "location manager failed with error \(error)" )
}
}
works for me on macOS if you say yes to the "enable location services" prompt when you launch the app the first time.
Note you also need the NSLocationWhenInUseUsageDescription property list entry as indicated in the documentation.
console output is (slightly obfuscated):
location manager auth status changed to: status not yet determined
location manager auth status changed to: status authorized location:
Optional(<+4X.48,-12X.62632228> +/- 65.00m (speed -1.00 mps /
course -1.00) # 11/3/18, 11:42:48 AM Pacific Daylight Time)

Get current location from osx/cocoa app using xcode swift 3

I apologize for not providing it with a code. The reason I posted it with images because xcode is LLVM(low level virtual machine) compiler which has mostly UI environment especially for the configuration part. Such as creating either an outlet or action for object by dragging them to the view controller instead defining it by a code directly. Thus since it was so, I think people would easily notice my mistake faster.
import Cocoa
import MapKit
class ViewController: NSViewController, CLLocationManagerDelegate {
#IBOutlet var mapView: MKMapView!
var locManager = CLLocationManager()
var currentLocation = CLLocation()
var locationManager = CLLocationManager()
var didFindMyLocation = false
var strForCurLatitude = "";
var strForCurLongitude = "";
override func viewDidLoad() {
super.viewDidLoad()
let distancespan:CLLocationDegrees = 2000
let busCScampuslocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(currentLocation.coordinate.latitude, currentLocation.coordinate.longitude)
mapView.setRegion(MKCoordinateRegion.init(center: bsuCScampuslocation, latitudinalMeters: distancespan, longitudinalMeters: distancespan), animated: true)
print(currentLocation.coordinate.latitude)
}
...
}
Swift 5:
ViewController.swift
import Cocoa
import CoreLocation
import MapKit
class ViewController: NSViewController, CLLocationManagerDelegate {
let manager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
//This is where you can update the MapView when the computer is moved (locations.last!.coordinate)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
print("location manager auth status changed to: " )
switch status {
case .restricted:
print("restricted")
case .denied:
print("denied")
case .authorized:
print("authorized")
case .notDetermined:
print("not yet determined")
default:
print("Unknown")
}
}
Add these lines to Info.plist or set the NSLocationAlwaysAndWhenInUseUsageDescription and/(or?) NSLocationUsageDescription to a plaintext description of why you need to access the users location
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Allows us to locate you</string>
<key>NSLocationUsageDescription</key>
<string>Allows us to locate you</string>
If the user has Location Services off, the authorization status will be 'denied'

Location always nil on WatchOS2

I'm trying to retrieve de Lat Long of my current position on my WatchOS2 app.
The app is authenticated and the iPhone app works fine.
info.plist: NSLocationWhenInUseUsageDescription is set.
I use the following code in the willActivate()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.AuthorizedWhenInUse)
{
locationManager.requestWhenInUseAuthorization()
print("Logon: Location not authorized1")
}
}
locationManager.requestLocation()
In the locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) i run this code:
if locationManager.location != nil
{
self.userLat = String(self.locationManager.location!.coordinate.latitude)
self.userLong = String(self.locationManager.location!.coordinate.longitude)
print("Lat\(userLat) Long \(userLong)")
}
else
{
print("An error occurred") <- It always prints this error
}
}
locationManager(manager: CLLocationManager, didFailWithError error: NSError)
is not being called
I've found a library which solved my problem. I don't know what exactly happend but it works now.
I used the OneShotLocationManger by icanzilb
I've you want to use this in your WatchOS2 app, you'll have to change startUpdatingLocation() to requestLocation(), because WatchOS doesn't allow continues updates.
like so:
//location authorization status changed
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .AuthorizedWhenInUse:
self.locationManager!.requestLocation()
case .Denied:
_didComplete(nil, error: NSError(domain: self.classForCoder.description(),
code: OneShotLocationManagerErrors.AuthorizationDenied.rawValue,
userInfo: nil))
default:
break
}
}
Give Credit Where Credit Is Due -

Resources