Get WiFi information on iOS with Swift

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.

 

Spread the love

2 thoughts on “Get WiFi information on iOS with Swift

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

Leave a Reply

Your email address will not be published. Required fields are marked *