iOS 13 wifi ssid in airplane mode - swift

I'm currently updating my application in regards to what is being returned from CNCopyCurrentNetworkInfo. I understand the privacy changes Apple implemented in regards to this starting on iOS 13 so i'm currently updating the implementation.
This is pretty straight forward. However the issue i'm running into is within this part of the app, the user will probably be in airplane mode (in-flight app). Regardless of the CLLocationManager.authorizationStatus(), even if it is .notDetermined which then triggers the requestWhenInUseAuthorization() method and once the user chooses either "Allow Once" or "Allow while Using App", i'm still not able to get the Wi-Fi ssid.
static func fetchSSIDInfo() -> String? {
if isSimulator() {
return "wireless"
} else {
if let interfaces: CFArray = CNCopySupportedInterfaces() {
for i in 0..<CFArrayGetCount(interfaces) {
let interfaceName: UnsafeRawPointer = CFArrayGetValueAtIndex(interfaces, i)
let rec = unsafeBitCast(interfaceName, to: AnyObject.self)
// skips this in airplane mode
if let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)" as CFString) {
if let interfaceData = unsafeInterfaceData as Dictionary? {
let ssid = interfaceData["SSID" as NSObject] as? String
let bssid = interfaceData["BSSID" as NSObject] as? String
if ssid != "Wi-Fi" && bssid != "00:00:00:00:00:00" {
return ssid
} else {
return "invalid"
}
}
}
}
}
}
return nil
}
In the code above, when in airplane mode it actually skips the if let unsafeInterfaceData. When it isn't in airplane mode, it's working as expected and returns either the ssid or the invalid string depending if user allows location services.
My question is how am I able to get this working on airplane mode? Maybe i'm missing something, but at this point not too sure.

In iOS13, and possibly earlier versions (that I do not have immediately available to test on), once you enable "Airplane mode," the WiFi is automatically disconnected.
An end-user would need to proactively reenable WiFi on their device to reconnect, while still having Airplane mode enabled.
Your if statement isn't getting executed, likely because there's no network information yet. Reenabling WiFi should get you the expected results.

Related

How can I get the list of all wifi that are near me on my Application in Swift, with Third Party Library

I am try to make an application which show all the near by wifi List on my application. The SSID of all the wifi on a tableview but there is very less information in it. If there is any third party library that can show this that would be helpful.
You can not. Apple did not provide list of near by wifi. But there is a way to get a list.
Apple provide list in special case. You have to follow certain steps and give some answers of questions with Apple support. You have to prove that you application is based on Wi-Fi management like Xender
For now you can get only connected SSID and BSSID of your connected AP(Access Point). and You will get ssid and bssid of connected hotspot in below or equivalent iOS 10.
for more info read doc and this
EDIT SWIFT 3
func printCurrentWifiInfo() {
if let interface = CNCopySupportedInterfaces() {
for i in 0..<CFArrayGetCount(interface) {
let interfaceName: UnsafeRawPointer = CFArrayGetValueAtIndex(interface, i)
let rec = unsafeBitCast(interfaceName, to: AnyObject.self)
if let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)" as CFString), let interfaceData = unsafeInterfaceData as? [String : AnyObject] {
// connected wifi
print("BSSID: \(interfaceData["BSSID"]), SSID: \(interfaceData["SSID"]), SSIDDATA: \(interfaceData["SSIDDATA"])")
} else {
// not connected wifi
}
}
}
}

How can I get IOBluetoothDevice's battery level, using Swift and AppKit (Xcode for MacOS)

I am using Xcode to develop a MacOS app, based on Cocoa & AppKit, written in Swift.
I am using IOBluetoothDevice objects throughout my app, and I want to be able to display the devices' battery levels, if accessible.
I expect that devices which battery levels are visible on the OS's Bluetooth settings (see image below), to be also accessible programmatically (e.g., AirPods, Magic Keyboard, etc.). However, I could not find this anywhere.
I have also thought about executing a terminal command and found this thread, but it did also not work.
Thanks
You can get the battery level of Bluetooth devices from the IORegistry with IOKit.
This is a simple example to get the battery level for the Magic Trackpad 2
import IOKit
var serialPortIterator = io_iterator_t()
var object : io_object_t
let port: mach_port_t
if #available(macOS 12.0, *) {
port = kIOMainPortDefault // New name in macOS 12 and higher
} else {
port = kIOMasterPortDefault // Old name in macOS 11 and lower
}
let matchingDict : CFDictionary = IOServiceMatching("AppleDeviceManagementHIDEventService")
let kernResult = IOServiceGetMatchingServices(port, matchingDict, &serialPortIterator)
if KERN_SUCCESS == kernResult {
repeat {
object = IOIteratorNext(serialPortIterator)
if object != 0, let percent = IORegistryEntryCreateCFProperty(object, "BatteryPercent" as CFString, kCFAllocatorDefault, 0).takeRetainedValue() as? Int {
print(percent)
break
}
} while object != 0
IOObjectRelease(object)
}
IOObjectRelease(serialPortIterator)
For other devices you have to replace AppleDeviceManagementHIDEventService and Trackpad2 with the appropriate values. You can display the entire IORegistry in Terminal.app with ioreg -l.

NEHotspotHelper.register not received call back iOS11

I am working on NEHotspotHelper and trying to register but not receiving call back. Firstly,
I enabled Capability : Network Extensions
Then added this following code,
let options: [String: NSObject] = [kNEHotspotHelperOptionDisplayName : "ABC" as NSObject]
let queue: DispatchQueue = DispatchQueue(label: "com.ABC", attributes: DispatchQueue.Attributes.concurrent)
NSLog("Started wifi scanning.")
NEHotspotHelper.register(options: options, queue: queue) { (cmd: NEHotspotHelperCommand) in
NSLog("Received command: \(cmd.commandType.rawValue)")
if cmd.commandType == NEHotspotHelperCommandType.filterScanList {
//Get all available hotspots
let list: [NEHotspotNetwork] = cmd.networkList!
//Figure out the hotspot you wish to connect to
print(list)
} else if cmd.commandType == NEHotspotHelperCommandType.evaluate {
if let network = cmd.network {
//Set high confidence for the network
network.setConfidence(NEHotspotHelperConfidence.high)
let response = cmd.createResponse(NEHotspotHelperResult.success)
response.setNetwork(network)
response.deliver() //Respond back
}
} else if cmd.commandType == NEHotspotHelperCommandType.authenticate {
//Perform custom authentication and respond back with success
// if all is OK
let response = cmd.createResponse(NEHotspotHelperResult.success)
response.deliver() //Respond back
}
}
Kindly let me know if I am missing any step.
You should check the result of the register() function. If it's returning false, something is probably not configured correctly. See the full list of configuration instructions below.
Also in the screenshot you provided, you have the entitlements enabled for Hotspot Configuration, but the API you're calling is for Hotspot Helper. The two features require very different entitlements. You'll need to make sure everything is configured for Hotspot Helper to call that API. Again, see below for full details. See Hotspot Helper vs. Hotspot Configuration for more details about the differences of these similarly named APIs.
To use NEHotspotHelper:
Apply for the Network Extension entitlement.
This needs to be done at Apple's website here.
Modify your Provisioning Profile.
Go to http://developer.apple.com. Hit Edit near your profile. On the bottom where it says Entitlements, choose the one that contains the Network Extension entitlement.
Update your app's entitlements file.
The application must set com.apple.developer.networking.HotspotHelper as one of its entitlements. The value of the entitlement is a boolean set to true.
Add Background Mode
The application's Info.plist must include a UIBackgroundModes array containing network-authentication.
Note that unlike all the other background modes that are converted to human readable strings, this one will stay as network-authentication.
Call the NEHotspotHelper.register() function.
This method should be called once when the application starts up. Invoking it again will have no effect and result in false being returned.
You should make sure the function returns true. Otherwise something one of the above steps is probably not configured properly.
Understand when this callback will be called.
From the documentation, it's not entirely clear when exactly this callback will be called. For example, one might assume that NEHotspotHelper could be used to monitor for network connections. However, the callback will (only?) be called when the user navigates to the Settings app and goes to the Wi-Fi page.
Since your callback will be called only while the user in the Settings app, you should attach to the debugger and use print().
Swift Example
let targetSsid = "SFO WiFi"
let targetPassword = "12345678"
let targetAnnotation: String = "Acme Wireless"
let options: [String: NSObject] = [
kNEHotspotHelperOptionDisplayName: targetAnnotation as NSString
]
let queue = DispatchQueue(label: "com.example.test")
let isAvailable = NEHotspotHelper.register(options: options, queue: queue) { (command) in
switch command.commandType {
case .evaluate,
.filterScanList:
let originalNetworklist = command.networkList ?? []
let networkList = originalNetworklist.compactMap { network -> NEHotspotNetwork? in
print("networkName: \(network.ssid); strength: \(network.signalStrength)")
if network.ssid == targetSsid {
network.setConfidence(.high)
network.setPassword(targetPassword)
return network
}
return nil
}
let response = command.createResponse(.success)
response.setNetworkList(networkList)
response.deliver()
default:
break
}
}
assert(isAvailable)
Sources:
https://developer.apple.com/documentation/networkextension/nehotspothelper/1618965-register
https://medium.com/#prvaghela/nehotspothelper-register-an-app-as-a-hotspot-helper-cf92a6ed7b72
https://stackoverflow.com/a/39189063/35690

Detect ethernet/wifi network change

I want to detect when the network changes from ethernet to wifi (or wifi to ethernet). I want to have an observer to notify me about this change.
reachability isn't good enough - it's always returns ReachableViaWiFi for both cases.
P.S -
There were some questions regarding this topic before, but none of them has a good answer, and since those questions are more than a year old, maybe someone already find out how to do it
You can access system network preferences through SystemConfiguration module, which helps you get touch to system preferences store currently resides in the default location /Library/Preferences/SystemConfiguration/preferences.plist.
Since then, you can receive notifications from SCDynamicStore by SCDynamicStoreNotifyValue(_:_:) or retrieve value by SCDynamicStoreCopyValue(_:_:).
Example for directly lookup current primary network service:
var store = SCDynamicStoreCreate(nil, "Example" as CFString, nil, nil)
var global = SCDynamicStoreCopyValue(store, "State:/Network/Global/IPv4" as CFString)!
var pref = SCPreferencesCreate(nil, "Example" as CFString, nil)
var service = SCNetworkServiceCopy(pref!, global["PrimaryService"] as! CFString)
var interface = SCNetworkServiceGetInterface(service!)
SCNetworkInterfaceGetInterfaceType(interface!) /// Optional("IEEE80211") -> Wi-Fi
Or create dynamic store with callback and set notification keys to receive notifications as every time primary network service changes the notification is going to fire:
var callback: SCDynamicStoreCallBack = { (store, _, _) in
/* Do anything you want */
}
var store = SCDynamicStoreCreate(nil, "Example" as CFString, callback, nil)
SCDynamicStoreSetNotificationKeys(store!, ["State:/Network/Global/IPv4"] as CFArray, nil)
Please note that a Mac can have multiple active interfaces at the same time and some of these may be Ethernet and some of them may be WiFi. Even if you just monitor the primary interfaces, take note that a Mac can have multiple primary interfaces, one per protocol (e.g. the primary interface for IPv4 may not be the primary one for IPv6).
For demonstration purposes, I will assume that you want to monitor the primary IPv4 interface. Here is code that you can just copy & paste to a swift file and directly run from command line (e.g. swift someFile.swift):
import Foundation
import SystemConfiguration
let DynamicStore = SCDynamicStoreCreate(
nil, "Name of your App" as CFString,
{ ( _, _, _ ) in PrimaryIPv4InterfaceChanged() }, nil)!
func PrimaryIPv4InterfaceChanged ( ) {
guard let ipv4State = SCDynamicStoreCopyValue(DynamicStore,
"State:/Network/Global/IPv4" as CFString) as? [CFString: Any]
else {
print("No primary IPv4 interface available")
return
}
guard let primaryServiceID =
ipv4State[kSCDynamicStorePropNetPrimaryService]
else { return }
let interfaceStateName =
"Setup:/Network/Service/\(primaryServiceID)/Interface"
as CFString
guard let primaryServiceState = SCDynamicStoreCopyValue(
DynamicStore, interfaceStateName) as? [CFString: Any]
else { return }
guard let hardwareType =
primaryServiceState[kSCPropNetInterfaceHardware]
else { return }
switch hardwareType as! CFString {
case kSCEntNetAirPort:
print("Primary IPv4 interface is now WiFi")
case kSCEntNetEthernet:
print("Primary IPv4 interface is now Ethernet")
default:
print("Primary IPv4 interface is something else")
}
}
SCDynamicStoreSetNotificationKeys(
DynamicStore, [ "State:/Network/Global/IPv4" ] as CFArray, nil)
SCDynamicStoreSetDispatchQueue(DynamicStore, DispatchQueue.main)
dispatchMain()
While it is running, try switching your primary IPv4 interface, pull network cables, turn off WiFi, etc. and watch the output. You can stop it by hitting CTRL+C on your keyboard.
You could run a little bash script under launchd that monitors the interfaces you are interested in and launches something when they change.
Say your wired connection is en0, you could run:
./netmon en0
Save this script as netmon and make it executable with chmod +x netmon
#!/bin/bash
interface=$1
# Get current status of interface whose name is passed, e.g. en0
status(){
ifconfig $1 | awk '/status:/{print $2}'
}
# Monitor interface until killed, echoing changes in status
previous=$(status $interface)
while :; do
current=$(status $interface)
if [ $current != $previous ]; then
echo $interface now $current
previous=$current
fi
sleep 5
done

iPhone Wifi on or off?

Within iOS framework, how can one check if the Wifi radio is enabled by the user or not? Please note that I'm not interested in the reachability through Wifi but rather if the device is turned off by the user. Thanks.
Based on: http://www.enigmaticape.com/blog/determine-wifi-enabled-ios-one-weird-trick
Wifi status can be determined to be ON/ OFF using C based ifaddress struct from:
ifaddrs.h, and
net/if.h
[Code source: unknown.]
- (BOOL) isWiFiEnabled {
NSCountedSet * cset = [NSCountedSet new];
struct ifaddrs *interfaces;
if( ! getifaddrs(&interfaces) ) {
for( struct ifaddrs *interface = interfaces; interface; interface = interface->ifa_next) {
if ( (interface->ifa_flags & IFF_UP) == IFF_UP ) {
[cset addObject:[NSString stringWithUTF8String:interface->ifa_name]];
}
}
}
return [cset countForObject:#"awdl0"] > 1 ? YES : NO;
}
Swift 3 version (requires bridging header with #include <ifaddrs.h>):
func isWifiEnabled() -> Bool {
var addresses = [String]()
var ifaddr : UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else { return false }
guard let firstAddr = ifaddr else { return false }
for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
addresses.append(String(cString: ptr.pointee.ifa_name))
}
freeifaddrs(ifaddr)
return addresses.contains("awdl0")
}
As mentioned in comment by #magma, you may have to use Reachability source code.
So far based on my experience and what others have been talking, there is NO boolean which can tell you if the user has turned off Wi-Fi in Settings. By checking if the device can reach internet, you just have to deduce and conclude(assume) the user has turned Wi-Fi off.
Using Reachability is the correct way to go.
You cannot access the direct settings within an app that should go on the App Store. That is considered private user territory where an app has no reason to deal with at all.
But without more explanation I do not really see the need to find out the settings.
That is nothing an App should ever be interested or worry about. You have network access or you do not have network access. If none is available then the reason for it does not matter.
You do not ask a question like "Do you not have a Ford?" "Do you not have a BMW?". Instead from a good application design you ask "Do you have a car?"
Also Raechability has nothing to do with the Internet. It tells you if the device is theoretical reachable by over a TCP/IP network. That means some network communication is available. Then you can check what type (e.g. Wifi vs. 3G/LTE). And that is exactly what's Reachability is for.
If you for what ever reason really want to go down if the radios is turned on and it is for a kind of Enterprise app, you can look into the private frameworks, import them and deal with their undocumented methods that are subject to change with any update.
For Wifi it should be: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/PrivateFrameworks/MobileWiFi.framework
iOS has no public API that tells you if Wi-Fi is on or off.
However, you can use the public API CNCopyCurrentNetworkInfo(), available in SystemConfiguration framework, to get info about the current Wi-Fi network. This does not tell you if Wi-Fi is turned on or off but rather if the device is joined to a Wi-Fi network or not. Perhaps this is sufficient for your purposes.
Also note that this API doesn't work in Simulator, as CNCopySupportedInterfaces() always returns NULL.
#include <SystemConfiguration/CaptiveNetwork.h>
BOOL hasWiFiNetwork = NO;
NSArray *interfaces = CFBridgingRelease(CNCopySupportedInterfaces());
for (NSString *interface in interfaces) {
NSDictionary *networkInfo = CFBridgingRelease(CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interface)));
if (networkInfo != NULL) {
hasWiFiNetwork = YES;
break;
}
}
Swift 2.3 version
func isWifiEnabled() -> Bool {
var addresses = [String]()
var ifaddr : UnsafeMutablePointer<ifaddrs> = nil
guard getifaddrs(&ifaddr) == 0 else { return false }
var ptr = ifaddr
while ptr != nil { defer { ptr = ptr.memory.ifa_next }
addresses.append(String.fromCString(ptr.memory.ifa_name)!)
}
var counts:[String:Int] = [:]
for item in addresses {
counts[item] = (counts[item] ?? 0) + 1
}
freeifaddrs(ifaddr)
return counts["awdl0"] > 1 ? true : false
}
Swift 4.0 version
func isWifiEnabled() -> Bool {
var addresses = [String]()
var ifaddr : UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else { return false }
guard let firstAddr = ifaddr else { return false }
for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
addresses.append(String(cString: ptr.pointee.ifa_name))
}
var counts:[String:Int] = [:]
for item in addresses {
counts[item] = (counts[item] ?? 0) + 1
}
freeifaddrs(ifaddr)
guard let count = counts["awdl0"] else { return false }
return count > 1
}
PrivateFrameworks is not available in sdk nor is it available to any enterprise developer.
If we know what you want to achieve my knowing the wifi radio power state, we can probably suggest an alternative solution, but from how apple has been with what they expose to developers i don't see this ever becoming possible directly.
Using reachability you can know for sure if wifi is ON but to know if wifi is OFF toss a coin.