Reading Time: 6 minutes
In a recent project one of the requirements was to fetch information about WiFi network you’re currently connected to (like ssid, bssid, rssi (signal strength) and channel) and also scan for all nearby WiFi networks.
So can we do this on iOS? Answer is not that simple and in this post I’ll explain what information you can get and how.
Scan nearby WiFi (SSID) networks
So on iOS, how can we scan for nearby WiFi networks? Short answer is you can’t as Apple does not allow you to scan for nearby SSIDs.
There is no public API you can use to access these informations. There are two workarounds but in my opinion neither of them are satisfactory as one will result in poor user experience while other will get you a rejection during AppStore submission process.
First workaround is to use NEHotspotHelper API by Apple (which requires a special entitlement from Apple) and other is to use a private library.
Apple’s official documentation says following about NEHotspotHelper – “The NEHotspotHelper interface allows Wi-Fi network implementers to facilitate connections to the large-scale wireless networks that they manage”.
But before you can use it you need to obtain a special entitlement from Apple explaining the cause. You can do this by filling a form available here. Note that it may take couple of days for Apple to approve or reject your application.
Sounds good, so what’s the problem? In case you get an approval by Apple it doesn’t mean you can now call some method in your project which will return all available WiFi networks around you. No, it means if user opens WiFi option under device Settings, once scanning is completed your app will get notified and you’ll receive informations about discovered WiFi networks. As this solution didn’t met project requirements I didn’t even try it but in case this solution satisfies your requirements here you can find little more information.
Other solution is to use private library but this will get your app banned from the store. So you can use this solution if you’re not going to submit your app on AppStore.
You can read more about this on Apple developer forum.
Get current Wi-Fi network information
What about fetching information regarding WiFi network you’re currently connected to, like ssid, bssid, rssi (signal strength), channel? Answer is yes and no because you can get information like ssid (network name) and bssid (MAC address) but not much more. There is also an undocumented way how you can get signal strength (RSSI) and I’ll show you how in this post.
Note – this won’t work on simulator, you need to test it on a real device.
First things first, let’s open Xcode and create a new project. After that select your target, click on “Capabilities” tab and there turn “Access WiFi Information” on.
To get basic informations about current WiFi network like name and Mac address we’ll use CNCopySupportedInterfaces and CNCopyCurrentNetworkInfo. As Apple says, CNCopySupportedInterfaces returns the names of all network interfaces Captive Network Support is monitoring (you can read more about it here). And CNCopyCurrentNetworkInfo returns the current network info for a given network interface (you can find more info here).
So first let’s create a model for our WiFi info and name it WiFiInfo.
struct WiFiInfo { var rssi: String var networkName: String var macAddress: String }
After that create a new class WiFiInfoService, open it and on top of it put following line:
import SystemConfiguration.CaptiveNetwork
Without this we can’t use CNCopySupportedInterfaces nor CNCopyCurrentNetworkInfo. Here is a method for fetching informations about our WiFi network.
func getWiFiInfo() -> WiFiInfo? { guard let interfaces = CNCopySupportedInterfaces() as? [String] else { return nil } var wifiInfo: WiFiInfo? = nil for interface in interfaces { guard let interfaceInfo = CNCopyCurrentNetworkInfo(interface as CFString) as NSDictionary? else { return nil } guard let ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String else { return nil } guard let bssid = interfaceInfo[kCNNetworkInfoKeyBSSID as String] as? String else { return nil } var rssi: Int = 0 if let strength = getWifiStrength() { rssi = strength } wifiInfo = WiFiInfo(rssi: "\(rssi)", networkName: ssid, macAddress: bssid) break } return wifiInfo }
As you can see first we’re trying to access network interfaces and after that we’re trying to get current network interface. In case we find our network interface we’re fetching data like ssid (name) and bssid (mac address). Once we get these informations we’ll try to get signal strength (rssi). As I already said there is no official way to do this but I found undocumented feature that gives you this data – and that is by accessing information in status bar. But note that fetching rssi from status bar differs on iPhone X and other iOS devices. On all devices except iPhone X you can get signal strength in dBm (decibel-milliwatts) while on iPhone X you’ll get number of active bars which we’ll then convert to dBm (note that higher value means better signal).
So in our getWiFiInfo() we’re calling method getWifiStrength() which looks like this:
private func getWifiStrength() -> Int? { return Helper.isiPhoneX() ? getWifiStrengthOnIphoneX() : getWifiStrengthOnDevicesExceptIphoneX() }
Here is a method which fetches rssi on all devices except iPhone X.
private func getWifiStrengthOnDevicesExceptIphoneX() -> Int? { var rssi: Int? guard let statusBar = UIApplication.shared.value(forKey:"statusBar") as? UIView, let foregroundView = statusBar.value(forKey:”foregroundView”) as? UIView else { return rssi } for view in foregroundView.subviews { if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view.isKind(of: statusBarDataNetworkItemView) { if let val = view.value(forKey:”wifiStrengthRaw”) as? Int { rssi = val break } } } return rssi }
Here we’re trying to access status bar and from there fetch status bar foreground view. Then we’re looping through foreground view subviews and trying to get rssi using key “wifiStrengthRaw”.
I suggest to define your keys elsewhere and not hardcode it like I did in this example. Create Constants file and add your keys there. For example:
enum AppKeys: String { case foregroundView = "foregroundView" case numberOfActiveBars = "numberOfActiveBars" case statusBar = "statusBar" case wifiStrengthRaw = "wifiStrengthRaw" }
Fetching signal strength on iPhone X is similar but after we get data (number of bars) we need to convert it to dBm.
I created following enum for converting number of bars to dBm (note – higher value means better signal):
enum WiFISignalStrength: Int { case weak = 0 case ok = 1 case veryGood = 2 case excellent = 3 func convertBarsToDBM() -> Int { switch self { case .weak: return -90 case .ok: return -70 case .veryGood: return -50 case .excellent: return -30 } } }
And here is a complete code for our class, in case you don’t want to write your own code, feel free to use it in your project:
import UIKit import SystemConfiguration.CaptiveNetwork enum WiFISignalStrength: Int { case weak = 0 case ok = 1 case veryGood = 2 case excellent = 3 func convertBarsToDBM() -> Int { switch self { case .weak: return -90 case .ok: return -70 case .veryGood: return -50 case .excellent: return -30 } } } class WiFiInfoService: NSObject { // MARK - WiFi info func getWiFiInfo() -> WiFiInfo? { guard let interfaces = CNCopySupportedInterfaces() as? [String] else { return nil } var wifiInfo: WiFiInfo? = nil for interface in interfaces { guard let interfaceInfo = CNCopyCurrentNetworkInfo(interface as CFString) as NSDictionary? else { return nil } guard let ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String else { return nil } guard let bssid = interfaceInfo[kCNNetworkInfoKeyBSSID as String] as? String else { return nil } var rssi: Int = 0 if let strength = getWifiStrength() { rssi = strength } wifiInfo = WiFiInfo(rssi: "\(rssi)", networkName: ssid, macAddress: bssid) break } return wifiInfo } // MARK - WiFi signal strength private func getWifiStrength() -> Int? { return Helper.isiPhoneX() ? getWifiStrengthOnIphoneX() : getWifiStrengthOnDevicesExceptIphoneX() } private func getWifiStrengthOnDevicesExceptIphoneX() -> Int? { var rssi: Int? guard let statusBar = UIApplication.shared.value(forKey: Constants.AppKeys.statusBar.rawValue) as? UIView, let foregroundView = statusBar.value(forKey: Constants.AppKeys.foregroundView.rawValue) as? UIView else { return rssi } for view in foregroundView.subviews { if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view.isKind(of: statusBarDataNetworkItemView) { if let val = view.value(forKey: Constants.AppKeys.wifiStrengthRaw.rawValue) as? Int { rssi = val break } } } return rssi } private func getWifiStrengthOnIphoneX() -> Int? { guard let numberOfActiveBars = getWiFiNumberOfActiveBars(), let bars = WiFISignalStrength(rawValue: numberOfActiveBars) else { return nil } return bars.convertBarsToDBM() } private func getWiFiNumberOfActiveBars() -> Int? { var numberOfActiveBars: Int? guard let containerBar = UIApplication.shared.value(forKey: Constants.AppKeys.statusBar.rawValue) as? UIView else { return nil } guard let statusBarModern = NSClassFromString("UIStatusBar_Modern"), containerBar.isKind(of: statusBarModern), let statusBar = containerBar.value(forKey: Constants.AppKeys.statusBar.rawValue) as? UIView else { return nil } guard let foregroundView = statusBar.value(forKey: Constants.AppKeys.foregroundView.rawValue) as? UIView else { return nil } for view in foregroundView.subviews { for v in view.subviews { if let statusBarWifiSignalView = NSClassFromString("_UIStatusBarWifiSignalView"), v.isKind(of: statusBarWifiSignalView) { if let val = v.value(forKey: Constants.AppKeys.numberOfActiveBars.rawValue) as? Int { numberOfActiveBars = val break } } } if let _ = numberOfActiveBars { break } } return numberOfActiveBars } }
Wrap up
As you see fetching information about WiFi network you’re currently connected to or scanning for nearby WiFi networks on iOS is not that simple or straightforward. So I hope this article was helpful and useful and that you learned something. If it was please share this article with your friends. And if you have any questions or problems leave me a comment or send an email.
Thanks for your post
how can i use this code??
what is isiphoneX() ?
And Helper is NEHotspotHelper??
Hi minu,
Thank you.
isiphoneX() is a function that determines which iPhone are you using, in this case are you using iPhone X because on iPhoneX you can’t get WiFi signal strength but number of bars – higher the number better the signal.
Helper in not a NEHotspotHelper, it’s my class with some useful methods like method for determining are you using iPhone X or not.
static func isiPhoneX() -> Bool {
if UIDevice().userInterfaceIdiom == .phone {
switch UIScreen.main.nativeBounds.height {
case 1136:
//print(“iPhone 5 or 5S or 5C”)
break
case 1334:
//print(“iPhone 6/6S/7/8”)
break
case 2208:
//print(“iPhone 6+/6S+/7+/8+”)
break
case 2436:
//print(“iPhone X”)
return true
default:
break
//print(“unknown”)
}
}
return false
}
This code is really helpful for my project!
This time is the first that make apple app using swift….TT
So some part of code is very difficult for me
1. What is the role of func getWiFiNumberOfActiveBars()?
2. Is this code just for connected wifi?
3. What is Constants class?
Hi Hanni,
Thank you.
1. On all devices except iPhone X you can get signal strength in dBm (decibel-milliwatts) while on iPhone X you’ll get number of active bars which then needs to be converted to dBm.
2. Yes.
3. Constants is a class that holds constants :), simple as that, in this case AppKeys enum which you can find in code above.
Hello, I recently read an article that says that during Apple’s WWDC 2019 developer session 713 titled, “Advances in Networking” it was revealed that iOS 13 will stop location tracking using your device’s SSID/BSSID using the CNCopyCurrentNetworkInfo API. Developers have reported getting an email from Apple that says:
Starting with iOS 13, the CNCopyCurrentNetworkInfo API will no longer return valid Wi-Fi SSID and BSSID information. Instead, the information returned by default will be:
SSID: “Wi-Fi” or “WLAN” (“WLAN” will be returned for the China SKU) BSSID: “00:00:00:00:00:00”
Have you heard this?
In that case, besides the user manually scanning or using the private library approach where you can’t get app store approval, will there be any other options for iOS 13 to get a valid BSSID?
Hi William,
Yes, I also read about this.
Unfortunately I’m currently pretty busy but I’m going to research this soon and update the article with possible solutions.
Hi,
How to call getWiFiInfo method in other class.
Hi Sofia,
It’s simple:
let service = WiFiInfoService()
let info = service.getWiFiInfo()
or better
if let info = service.getWiFiInfo() {
// do something with data
} else {
// present alert
}