Hi, I've read a bunch of threads regarding the changes in Sonoma and later requiring Location permission for receiving SSIDs. However, as far as I can see, in Sequoia 15.1 SSIDs and BSSIDs are empty regardless.
In particular, this makes it not possible to use associate(withName:)
and associate(withSSID:)
because the network object returned by scanForNetwork(withSSID: "...")
has its .ssid
and .bssid
set to nil.
Here is an example:
- First we have a wrapper to call the code after the location permission is authorized:
import Foundation
import CoreLocation
class LocationDelegate: NSObject, CLLocationManagerDelegate {
var onAuthorized: (() -> Void)?
var onDenied: (() -> Void)?
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let authStatus = manager.authorizationStatus
print("Location authorization status changed: \(authStatusToString(authStatus))")
if authStatus == .authorizedAlways {
onAuthorized?()
} else if authStatus == .denied || authStatus == .restricted {
onDenied?()
}
}
}
let locationManager = CLLocationManager()
let locationDelegate = LocationDelegate()
func authorizeLocation(onAuthorized: @escaping () -> Void, onDenied: @escaping () -> Void) {
let authStatus = locationManager.authorizationStatus
print("Location authorization status: \(authStatusToString(authStatus))")
if authStatus == .notDetermined {
print("Waiting for location authorization...")
locationDelegate.onAuthorized = onAuthorized
locationDelegate.onDenied = onDenied
locationManager.delegate = locationDelegate
locationManager.requestAlwaysAuthorization()
} else if authStatus == .authorizedAlways {
onAuthorized()
} else if authStatus == .denied || authStatus == .restricted {
onDenied()
}
RunLoop.main.run()
}
func authStatusToString(_ status: CLAuthorizationStatus) -> String {
switch status {
case .notDetermined:
return "Not Determined"
case .restricted:
return "Restricted"
case .denied:
return "Denied"
case .authorizedAlways:
return "Always Authorized"
case .authorizedWhenInUse:
return "Authorized When In Use"
@unknown default:
return "Unknown"
}
}
- Then, a demo program itself:
import Foundation
import CoreWLAN
import Network
let client = CWWiFiClient.shared()
guard let interface = client.interface() else {
print("No wifi interface")
exit(1)
}
authorizeLocation(
onAuthorized: {
do {
print("Scanning for wifi networks...")
let scanResults = try interface.scanForNetworks(withSSID: nil)
let networks = scanResults.compactMap { network -> [String: Any]? in
return [
"ssid": network.ssid ?? "unknown",
"bssid": network.bssid ?? "unknown"
]
}
let jsonData = try JSONSerialization.data(withJSONObject: networks, options: .prettyPrinted)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
exit(0)
} catch {
print("Error: \(error)")
exit(1)
}
},
onDenied: {
print("Location access denied")
exit(1)
}
)
When launched, the program asks for permission, and after that, is shown as enabled in Privacy & Security Settings panel.
Here is the output where it can be seen that the scan is performed after location access was authorized, and regardless of that, all ssids are empty:
Location authorization status: Not Determined
Waiting for location authorization...
Location authorization status changed: Always Authorized
Scanning for wifi networks...
[
{
"ssid" : "unknown",
"bssid" : "unknown"
},
{
"ssid" : "unknown",
"bssid" : "unknown"
},
.... further omitted
Calling scanForNetworks()
with explicitly specified network name does this as well, returns a CWNetwork
object with .ssid / .bssid = nil
.